{"record":{"id":"21f243438f619626","repo":"FuelLabs/fuel-core","slug":"cannot-repair-block-because-fencing-token-is-not-i","errorCode":null,"errorMessage":"Cannot repair block because fencing token is not initialized","messagePattern":"Cannot repair block because fencing token is not initialized","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/fuel-core/src/service/adapters/consensus_module/poa.rs","lineNumber":1176,"sourceCode":"    /// Uses `publish_block_on_all_nodes` which runs `write_block.lua`:\n    /// - Written: node accepted the block (counted toward quorum)\n    /// - HEIGHT_EXISTS: node has *some* block at this height — may be a\n    ///   different block from a competing partial write, so NOT counted\n    /// - FENCING_ERROR: lost the lock — abort the repair\n    /// - The total (pre_existing + newly written) must reach quorum\n    fn repair_sub_quorum_block(\n        &self,\n        block: &SealedBlock,\n        pre_existing_count: usize,\n    ) -> anyhow::Result<bool> {\n        let epoch = match *self\n            .current_epoch_token\n            .lock()\n            .map_err(|_| anyhow!(\"cannot access epoch token, poisoned lock\"))?\n        {\n            Some(epoch) => epoch,\n            None => {\n                return Err(anyhow!(\n                    \"Cannot repair block because fencing token is not initialized\"\n                ));\n            }\n        };\n        let block_data = postcard::to_allocvec(block)?;\n        // Start from the pre-existing count (nodes already confirmed to\n        // have this specific block during reconciliation). Only count\n        // newly Written nodes — HeightExists means the node has *some*\n        // block at this height, but it might be a different block from\n        // a competing leader's partial write.\n        let mut total_with_block = pre_existing_count;\n        for result in self.publish_block_on_all_nodes(epoch, block, &block_data) {\n            match result {\n                Ok(WriteBlockResult::Written) => {\n                    total_with_block = total_with_block.saturating_add(1);\n                }\n                Ok(WriteBlockResult::HeightExists) => {\n                    // Node has some block at this height — may or may","sourceCodeStart":1158,"sourceCodeEnd":1194,"githubUrl":"https://github.com/FuelLabs/fuel-core/blob/b9d4d170da3a31c9ace5f963d633b326348e0d42/crates/fuel-core/src/service/adapters/consensus_module/poa.rs#L1158-L1194","documentation":"repair_sub_quorum_block needs the fencing epoch to stamp its Lua writes; current_epoch_token is None, meaning this node never acquired the Redis leader lease (or already released it via release_if_owner). Without a valid epoch the fencing checks in write_block.lua cannot prove write ownership, so repair is refused before any I/O.","triggerScenarios":"Reconciliation on a node whose can_produce_block/acquire_lease_if_free never succeeded (or whose release_if_owner already cleared the token) encounters partial writes attributed to this node and attempts repair.","commonSituations":"A node that lost leadership before finishing its writes tries to reconcile; or reconciliation runs on startup before the first lease acquisition completes; or test setups that call reconciliation without running leader election.","solutions":["Ensure sequencing: acquire the lease first (can_produce_block) and only then run reconciliation/repair on this node.","If leadership is gone, skip repair — the actual lease holder's reconciliation will repair or supersede these partial writes.","Investigate the ordering bug if reconciliation still considers this node responsible while its token is None (e.g. repair invoked after release_if_owner).","In tests, run one successful acquire_lease_if_free before exercising repair."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before reconciliation/repair: confirm this node actually holds the lease\n// (token initialized) — e.g. drive can_produce_block() to true first.\nif !adapter.can_produce_block().await? {\n    // Not the leader — skip repair; the lease holder will reconcile.\n    return Ok(());\n}","typeGuard":null,"tryCatchPattern":"// If the error still surfaces, map it to 'skip this block' rather than\n// failing the whole reconciliation loop.\nif let Err(e) = repair_result {\n    if e.to_string().contains(\"fencing token is not initialized\") {\n        continue; // next block; not fatal\n    }\n    return Err(e);\n}","preventionTips":["Run leader election to completion before any reconciliation that may repair your own writes.","Never invoke repair paths after release_if_owner on the same process lifetime without re-acquiring.","In tests, always seed the lease (acquire once) before exercising repair."],"tags":["redis","lease","fencing","reconciliation","sequencing"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}