linera-io/linera-protocol · warning · ChainError

NotTimedOutYet

NotTimedOutYet

Error message

Not signing timeout certificate; current round times out at time {0}

What it means

create_timeout_vote refuses to sign a timeout before the round's deadline: the validator's local time must have reached round_timeout, the timestamp stored when the round opened. The error returns that deadline so callers know when to come back.

Source

Thrown at linera-chain/src/manager.rs:394

        &mut self,
        chain_id: ChainId,
        height: BlockHeight,
        round: Round,
        epoch: Epoch,
        key_pair: Option<&ValidatorSecretKey>,
        local_time: Timestamp,
    ) -> Result<bool, ChainError> {
        let Some(key_pair) = key_pair else {
            return Ok(false); // We are not a validator.
        };
        ensure!(
            round == self.current_round(),
            ChainError::WrongRound(self.current_round())
        );
        let Some(round_timeout) = *self.round_timeout.get() else {
            return Err(ChainError::RoundDoesNotTimeOut);
        };
        ensure!(
            local_time >= round_timeout,
            ChainError::NotTimedOutYet(round_timeout)
        );
        if let Some(vote) = self.timeout_vote.get() {
            if vote.round == round {
                return Ok(false); // We already signed this timeout.
            }
        }
        let value = Timeout::new(chain_id, height, epoch);
        self.timeout_vote
            .set(Some(Vote::new(value, round, key_pair)));
        Ok(true)
    }

    /// Signs a `Timeout` certificate to switch to fallback mode.
    ///
    /// This must only be called after verifying that the condition for fallback mode is
    /// satisfied locally.

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Wait until the round_timeout timestamp returned in the error, then retry the leader-timeout query
  2. Verify clock synchronization (NTP) between client and validator
  3. Derive wait durations from the validator's own timestamps in ChainInfoResponse rather than local assumptions

Example fix

// before: immediate retry, fails with NotTimedOutYet(round_timeout)
client.request_leader_timeout(chain_id, height, round).await?;

// after: honor the deadline returned by the error (or chain info), then retry
match client.request_leader_timeout(chain_id, height, round).await {
    Err(e) if matches!(e, ref x if x.is_not_timed_out_yet()) => {
        let deadline = e.not_timed_out_yet_deadline().unwrap();
        tokio::time::sleep_until(deadline.into()).await;
        client.request_leader_timeout(chain_id, height, round).await?;
    }
    other => other?,
}
Defensive patterns

Strategy: retry

Validate before calling

// Read the round deadline from chain info before asking for a timeout vote.
let info = client.chain_info(chain_id).await?;
if let Some(deadline) = info.manager.round_timeout {
    if clock.now() < deadline {
        tokio::time::sleep_until(deadline.into()).await;
    }
}
client.request_leader_timeout(chain_id, height, round).await?;

Type guard

fn is_not_timed_out_yet(e: &ChainError) -> bool {
    matches!(e, ChainError::NotTimedOutYet(_))
}

Try / catch

match client.request_leader_timeout(chain_id, height, round).await {
    Err(ChainError::NotTimedOutYet(deadline)) => {
        tokio::time::sleep_until(deadline.into()).await;
        client.request_leader_timeout(chain_id, height, round).await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: vote_for_leader_timeout / create_timeout_vote invoked with local_time < round_timeout — requesting a timeout vote too early in the current round.

Common situations: Client clock ahead of the validator's clock; retry loop that does not respect the deadline; round_timeout duration configured longer on the validator than the client assumes; tests with mocked time.

Understand the failure class

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/180dadce34bd9bfe. Report an issue: GitHub.