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
- 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).
- If this node is a passive follower/validator-only observer, set trigger: Trigger::Never so production paths are never entered.
- 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
- Run producing nodes with an explicit consensus key in config; never rely on defaults carrying over across config refactors.
- Add a startup check: signer.is_available() must be true unless trigger is Never.
- For observer nodes, always set Trigger::Never so production paths are never entered.
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
- unable to produce blocks without a signer
- Manual block production is not allowed with trigger `Open`
- Time exceeds system limits
- The provided time parameters lead to an overflow
- Block production timed out
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/013543e803b20842.
Report an issue: GitHub.