FuelLabs/fuel-core · critical

unable to produce blocks without a signer

Error message

unable to produce blocks without a signer

What it means

produce_predefined_block — the path that re-executes blocks yielded by the PredefinedBlocks port — checks self.signer.is_available() first, mirroring produce_block. Predefined blocks still need a signature over the produced header, so without a loaded consensus key the predefined-block production loop fails and the node cannot reproduce its scripted chain.

Source

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

        // Update last block time
        self.last_height = height;
        self.last_timestamp = block_time;
        self.last_block_created = match self.trigger {
            Trigger::Open { .. } => deadline.max(last_block_created),
            _ => last_block_created,
        };

        Ok(())
    }

    async fn produce_predefined_block(
        &mut self,
        predefined_block: &Block,
    ) -> anyhow::Result<()> {
        tracing::info!("Producing predefined block");
        let last_block_created = Instant::now();
        if !self.signer.is_available() {
            return Err(anyhow!("unable to produce blocks without a signer"))
        }

        // Ask the block producer to create the block
        let (
            ExecutionResult {
                block,
                skipped_transactions,
                tx_status,
                events,
            },
            changes,
        ) = self
            .block_producer
            .produce_predefined_block(predefined_block)
            .await?
            .into();

        if !skipped_transactions.is_empty() {

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Set consensus_signer / Config.signer to SignMode::Key(secret) for any node that produces predefined blocks.
  2. Disable the predefined blocks source if this node should not reproduce the scripted chain.
  3. Assert signer.is_available() at startup before the predefined-blocks service begins.

Example fix

// before
let config = Config {
    signer: SignMode::Unavailable,
    ..Default::default()
};

// after
let config = Config {
    signer: SignMode::Key(Secret::new(predefined_chain_key.into())),
    ..Default::default()
};
Defensive patterns

Strategy: validation

Validate before calling

// Precondition for any predefined-block node
use fuel_core_types::signer::SignMode;

fn assert_predefined_blocks_signable(signer: &SignMode, has_predefined_blocks: bool) -> anyhow::Result<()> {
    if has_predefined_blocks && !matches!(signer, SignMode::Key(_)) {
        anyhow::bail!("predefined block production requires an available signer");
    }
    Ok(())
}

Type guard

fn can_sign(signer: &SignMode) -> bool {
    matches!(signer, SignMode::Key(_))
}

Try / catch

// Service-level error: log and halt the predefined-blocks flow rather than looping
if let Err(err) = run_predefined_blocks(&mut task).await {
    if err.to_string().contains("without a signer") {
        tracing::error!("consensus key missing; predefined-block chain halted");
        return Err(err);
    }
    return Err(err);
}

Prevention

When it happens

Trigger: A node configured with predefined blocks (PB: PredefinedBlocks returning blocks, e.g. deterministic test chains) whose signer is SignMode::Unavailable when the predefined block production round starts.

Common situations: Test/dev chain configs enabling predefined blocks while overriding consensus_signer to Unavailable; provisioning scripts that start predefined-block nodes before the key is available; forking test setups where the signer field was removed from Config.

Related errors


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