{"record":{"id":"4d4b48696b81403b","repo":"FuelLabs/fuel-core","slug":"manual-block-production-is-not-allowed-with-trigge","errorCode":null,"errorMessage":"Manual block production is not allowed with trigger `Open`","messagePattern":"Manual block production is not allowed with trigger `Open`","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/services/consensus_module/poa/src/service.rs","lineNumber":337,"sourceCode":"        deadline: Instant,\n    ) -> anyhow::Result<()> {\n        self.produce_block(\n            self.next_height(),\n            self.next_time(RequestType::Trigger)?,\n            TransactionsSource::TxPool,\n            deadline,\n        )\n        .await\n    }\n\n    async fn produce_manual_blocks(\n        &mut self,\n        block_production: ManualProduction,\n    ) -> anyhow::Result<()> {\n        // The caller of manual block production expects that it is resolved immediately\n        // while in the case of `Trigger::Open` we need to wait for the period to pass.\n        if matches!(self.trigger, Trigger::Open { .. }) {\n            return Err(anyhow!(\n                \"Manual block production is not allowed with trigger `Open`\"\n            ))\n        }\n\n        let mut block_time = if let Some(time) = block_production.start_time {\n            time\n        } else {\n            self.next_time(RequestType::Manual)?\n        };\n        match block_production.mode {\n            Mode::Blocks { number_of_blocks } => {\n                for _ in 0..number_of_blocks {\n                    self.produce_block(\n                        self.next_height(),\n                        block_time,\n                        TransactionsSource::TxPool,\n                        Instant::now(),\n                    )","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/FuelLabs/fuel-core/blob/b9d4d170da3a31c9ace5f963d633b326348e0d42/crates/services/consensus_module/poa/src/service.rs#L319-L355","documentation":"produce_manual_blocks rejects every manual request while the node runs Trigger::Open. With Open, block production stays open for `period` seconds and the block is only finalized when the period elapses, so the manual request cannot be resolved immediately as SharedState::manually_produce_block's oneshot caller expects. The guard is an explicit config-combination check, not a runtime failure.","triggerScenarios":"Calling SharedState::manually_produce_block(start_time, Mode::Blocks{..} | Mode::BlockWithTransactions(..)) on a node whose Config.trigger is Trigger::Open { period } (crates/services/consensus_module/poa/src/service.rs:336). Typical in integration tests that reuse an Open-configured Service and then force blocks.","commonSituations":"Test harnesses configured for Trigger::Open (e.g. testing pre-confirmation flows) that also call manual production helpers; config migrated from Interval to Open while old test code still forces blocks; newer fuel-core versions where Trigger::Open was introduced and tests were not updated.","solutions":["Switch the node's trigger to Trigger::Interval { block_time } or Trigger::Instant in the PoA config when tests/tooling need manual production.","Do not call manually_produce_block against Open-configured nodes; wait for the open period to close the block instead.","Gate the call up front: check the configured Trigger before sending a Request::ManualBlocks."],"exampleFix":"// before\nlet config = Config {\n    trigger: Trigger::Open { period: Duration::from_secs(5) },\n    ..Default::default()\n};\n// ...\nshared_state.manually_produce_block(None, Mode::Blocks { number_of_blocks: 1 }).await?; // Err\n\n// after\nlet config = Config {\n    trigger: Trigger::Interval { block_time: Duration::from_secs(5) },\n    ..Default::default()\n};\n// ...\nshared_state.manually_produce_block(None, Mode::Blocks { number_of_blocks: 1 }).await?; // Ok","handlingStrategy":"validation","validationCode":"// Check before sending the manual request\nfn manual_production_allowed(trigger: &fuel_core_poa::Trigger) -> bool {\n    !matches!(trigger, fuel_core_poa::Trigger::Open { .. })\n}\n\nif !manual_production_allowed(&config.trigger) {\n    anyhow::bail!(\"node uses Trigger::Open; manual block production is rejected\");\n}\nshared_state.manually_produce_block(start_time, mode).await?;","typeGuard":null,"tryCatchPattern":"match shared_state.manually_produce_block(start_time, mode).await {\n    Err(err) if err.to_string().contains(\"Manual block production is not allowed\") => {\n        // config bug: switch trigger to Interval/Instant or wait for the open period\n    }\n    other => other,\n}","preventionTips":["In test harnesses, pick one strategy per node: Trigger::Open for period-based tests, Interval/Instant where tests force blocks manually.","Assert the trigger variant in test setup before any manually_produce_block call.","Treat this error as a permanent config mismatch — retrying the identical request always fails."],"tags":["consensus","poa","manual-block-production","configuration","rust"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}