linera-io/linera-protocol · error · WorkerError

InvalidEpoch

InvalidEpoch

Error message

Unexpected epoch {epoch}: chain {chain_id} is at {chain_epoch}

What it means

process_timeout only accepts a timeout certificate whose epoch equals the chain's current epoch: the certificate's signatures were collected from a specific committee, and validators of a different epoch are no longer authoritative for this chain.

Source

Thrown at linera-core/src/chain_worker/state.rs:797

    ))]
    pub(crate) async fn process_timeout(
        &mut self,
        certificate: TimeoutCertificate,
    ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
        // Check that the chain is active and ready for this timeout.
        // Verify the certificate. Returns a catch-all error to make client code more robust.
        self.initialize_and_save_if_needed().await?;
        let (chain_epoch, committee) = self.chain.current_committee().await?;
        certificate.check(&committee)?;
        if self
            .chain
            .tip_state
            .get()
            .already_validated_block(certificate.inner().height())?
        {
            return Ok((self.chain_info_response().await?, NetworkActions::default()));
        }
        ensure!(
            certificate.inner().epoch() == chain_epoch,
            WorkerError::InvalidEpoch {
                chain_id: certificate.inner().chain_id(),
                chain_epoch,
                epoch: certificate.inner().epoch()
            }
        );
        let old_round = self.chain.manager.current_round();
        self.chain
            .manager
            .handle_timeout_certificate(certificate, self.storage.clock().current_time());
        self.save().await?;
        let actions = self.create_network_actions(Some(old_round)).await?;
        Ok((self.chain_info_response().await?, actions))
    }

    /// Tries to load all blobs published in this proposal.
    ///

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Bring the node's admin-chain state up to date so chain_epoch matches the certificate, then retry
  2. Re-create the timeout certificate under the current epoch's committee
  3. During epoch migrations, order operations so epoch-creating admin blocks are processed before dependent certificates are submitted
Defensive patterns

Strategy: validation

Validate before calling

// Compare epochs before submitting a timeout certificate.
let info = client.chain_info(chain_id).await?;
let (chain_epoch, _) = current_committee_of(&info)?;
if certificate.inner().epoch() != chain_epoch {
    // Sync admin-chain state first, or mint a fresh certificate this epoch.
    return sync_admin_chain(&client).await;
}
client.submit_timeout_certificate(chain_id, certificate).await?;

Type guard

fn is_invalid_epoch(e: &WorkerError) -> bool {
    matches!(e, WorkerError::InvalidEpoch { .. })
}

Prevention

When it happens

Trigger: Submitting a TimeoutCertificate minted under the previous committee after the chain already transitioned to a new epoch (or the certificate is from a future epoch the chain has not entered).

Common situations: Committee/epoch migration in progress; the receiving validator has not yet processed the latest admin-chain blocks that create the new epoch; replaying certificates captured before an epoch change; uneven admin-chain sync across validators.

Related errors


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