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

unable to produce blocks without a consensus key

Error message

unable to produce blocks without a consensus key

What it means

produce_block verifies self.signer.is_available() before doing anything: PoA blocks must be signed, and the signer (fuel_core_types::signer::SignMode) only reports available when it actually holds a secret key. If the node reaches a production path (trigger firing or manual production) with SignMode::Unavailable, block production fails immediately and the chain stalls at the current height.

Source

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

                    Instant::now(),
                )
                .await?;
            }
        }
        Ok(())
    }

    async fn produce_block(
        &mut self,
        height: BlockHeight,
        block_time: Tai64,
        source: TransactionsSource,
        deadline: Instant,
    ) -> anyhow::Result<()> {
        let last_block_created = Instant::now();
        // verify signing key is set
        if !self.signer.is_available() {
            return Err(anyhow!("unable to produce blocks without a consensus key"))
        }

        if self.last_timestamp > block_time {
            return Err(anyhow!("The block timestamp should monotonically increase"))
        }
        // Ask the block producer to create the block
        let (
            ExecutionResult {
                block,
                skipped_transactions,
                tx_status,
                events,
            },
            changes,
        ) = self
            .signal_produce_block(height, block_time, source, deadline)
            .await?
            .into();

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Configure the signing key: set consensus_signer to SignMode::Key(secret) in the node config (or run with the local/dev defaults which embed default_consensus_dev_key).
  2. If this node is a passive follower/validator-only observer, set trigger: Trigger::Never so production paths are never entered.
  3. Fail fast at startup: log or assert signer.is_available() before starting the service when the trigger is not Never.

Example fix

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

// after (producing node)
let config = Config {
    signer: SignMode::Key(Secret::new(secret_key_bytes.into())),
    trigger: Trigger::default(),
    ..Default::default()
};

// or (passive node)
let config = Config {
    signer: SignMode::Unavailable,
    trigger: Trigger::Never,
    ..Default::default()
};
Defensive patterns

Strategy: validation

Validate before calling

// Fail fast at startup when the node is expected to produce
use fuel_core_types::signer::SignMode;

fn assert_signer_for_production(signer: &SignMode, trigger: &fuel_core_poa::Trigger) -> anyhow::Result<()> {
    if !matches!(trigger, fuel_core_poa::Trigger::Never) && !matches!(signer, SignMode::Key(_)) {
        anyhow::bail!("trigger {trigger:?} requires a consensus key, but the signer is unavailable");
    }
    Ok(())
}

Type guard

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

Try / catch

match node.manually_produce_block(None, mode).await {
    Err(err) if err.to_string().contains("without a consensus key") => {
        // provisioning error: load the key or set Trigger::Never; do not retry
        std::process::exit(1);
    }
    other => other,
}

Prevention

When it happens

Trigger: Any call to produce_block: default Trigger::Instant firing on new transactions, Trigger::Interval/Open ticks, or manually_produce_block — while Config.signer / node Config.consensus_signer is SignMode::Unavailable (no key loaded). Note Trigger defaults to Instant, so merely starting a node without a key is enough to hit this on the first tx.

Common situations: Custom node configs that drop the local/dev default consensus key (crates/fuel-core/src/service/config.rs:289 defaults to default_consensus_dev_key); operators forgetting to provision the consensus key secret for a producing validator; test setups constructing PoA Config with SignMode::Unavailable but leaving the trigger active.

Related errors


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