FuelLabs/fuel-core · error · anyhow::Error

Block production timed out

Error message

Block production timed out

What it means

Thrown by the PoA main task in signal_produce_block: the BlockProducer::produce_and_execute_block future is wrapped in tokio::time::timeout(self.production_timeout, ...) (Config.production_timeout, default 20s). If selecting transactions from the pool plus executing the whole block does not finish within that budget, the future is dropped and this anyhow error aborts the production attempt. The error propagates out of produce_block/produce_next_block and the block for that height is simply not produced.

Source

Thrown at crates/services/consensus_module/poa/src/service.rs:307

    C: GetTime,
    RS: WaitForReadySignal,
    RP: BlockReconciliationReadPort,
{
    // Request the block producer to make a new block, and return it when ready
    async fn signal_produce_block(
        &self,
        height: BlockHeight,
        block_time: Tai64,
        source: TransactionsSource,
        deadline: Instant,
    ) -> anyhow::Result<UncommittedExecutionResult<Changes>> {
        let future = self
            .block_producer
            .produce_and_execute_block(height, block_time, source, deadline);

        let result = tokio::time::timeout(self.production_timeout, future)
            .await
            .map_err(|_| anyhow::anyhow!("Block production timed out"))??;

        // In the case if the block production finished before the deadline
        // we need to wait until the deadline is reached to guarantee
        // the correct interval between blocks
        sleep_until(deadline).await;

        Ok(result)
    }

    pub(crate) async fn produce_next_block(
        &mut self,
        deadline: Instant,
    ) -> anyhow::Result<()> {
        self.produce_block(
            self.next_height(),
            self.next_time(RequestType::Trigger)?,
            TransactionsSource::TxPool,
            deadline,

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Increase Config.production_timeout (crates/services/consensus_module/poa/src/config.rs:14, default Duration::from_secs(20)) to comfortably exceed worst-case execution time for your block_gas_limit.
  2. Lower block_gas_limit (and block transaction count) so worst-case execution fits inside the timeout.
  3. Profile the producer/executor: check disk latency, executor metrics, and whether skipped-transaction processing dominates; fix the underlying slowness rather than only raising the timeout.
  4. Drain or cap the transaction pool backlog so each block's execution set is bounded.
  5. Treat a single timeout as transient: the next trigger tick retries production at the same height.

Example fix

// before (PoA Config)
let config = Config {
    production_timeout: Duration::from_millis(500),
    ..Default::default()
};

// after
let config = Config {
    production_timeout: Duration::from_secs(20),
    ..Default::default()
};
Defensive patterns

Strategy: retry

Validate before calling

// Before starting the service, sanity-check that the timeout can cover worst-case execution
// for your block gas limit (rough heuristic: measure an empty block first).
fn validate_production_timeout(config: &fuel_core_poa::Config, worst_case_execution: Duration) -> anyhow::Result<()> {
    anyhow::ensure!(
        config.production_timeout > worst_case_execution,
        "production_timeout ({:?}) must exceed worst-case execution ({:?})",
        config.production_timeout, worst_case_execution
    );
    Ok(())
}

Try / catch

// Timeout is transient (dropped future, no partial commit): match the message, back off, and let the next trigger retry.
match produce_result {
    Ok(()) => {}
    Err(err) if err.to_string().contains("Block production timed out") => {
        tracing::warn!("block production timed out; retrying on next trigger");
        // do not restart the node; await the next trigger tick / re-issue manual production
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Trigger-driven production (Trigger::Interval/Instant/Open firing produce_next_block) or manual production where produce_and_execute_block exceeds production_timeout. Concretely: a stuffed txpool with a high block_gas_limit, slow VM execution or storage I/O, DA compression enabled adding latency, or a config where production_timeout was lowered (e.g. to 500ms) while blocks legitimately take seconds.

Common situations: Dev/test chains with huge block_gas_limit and a flooded mempool; CI runners or low-spec VMs with slow disks; misconfigured production_timeout copied from another node's config; fuel-core version upgrades that made execution slower (new tx types, more consensus rules).

Understand the failure class

Related errors


AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16). Data as JSON: /api/errors/0dc1188787da06d5. Report an issue: GitHub.