FuelLabs/fuel-core · error · anyhow::Error
Cannot repair block because fencing token is not initialized
Error message
Cannot repair block because fencing token is not initialized
What it means
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.
Source
Thrown at crates/fuel-core/src/service/adapters/consensus_module/poa.rs:1176
/// Uses `publish_block_on_all_nodes` which runs `write_block.lua`:
/// - Written: node accepted the block (counted toward quorum)
/// - HEIGHT_EXISTS: node has *some* block at this height — may be a
/// different block from a competing partial write, so NOT counted
/// - FENCING_ERROR: lost the lock — abort the repair
/// - The total (pre_existing + newly written) must reach quorum
fn repair_sub_quorum_block(
&self,
block: &SealedBlock,
pre_existing_count: usize,
) -> anyhow::Result<bool> {
let epoch = match *self
.current_epoch_token
.lock()
.map_err(|_| anyhow!("cannot access epoch token, poisoned lock"))?
{
Some(epoch) => epoch,
None => {
return Err(anyhow!(
"Cannot repair block because fencing token is not initialized"
));
}
};
let block_data = postcard::to_allocvec(block)?;
// Start from the pre-existing count (nodes already confirmed to
// have this specific block during reconciliation). Only count
// newly Written nodes — HeightExists means the node has *some*
// block at this height, but it might be a different block from
// a competing leader's partial write.
let mut total_with_block = pre_existing_count;
for result in self.publish_block_on_all_nodes(epoch, block, &block_data) {
match result {
Ok(WriteBlockResult::Written) => {
total_with_block = total_with_block.saturating_add(1);
}
Ok(WriteBlockResult::HeightExists) => {
// Node has some block at this height — may or mayView on GitHub (pinned to b9d4d170da)
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.
Defensive patterns
Strategy: validation
Validate before calling
// Before reconciliation/repair: confirm this node actually holds the lease
// (token initialized) — e.g. drive can_produce_block() to true first.
if !adapter.can_produce_block().await? {
// Not the leader — skip repair; the lease holder will reconcile.
return Ok(());
} Try / catch
// If the error still surfaces, map it to 'skip this block' rather than
// failing the whole reconciliation loop.
if let Err(e) = repair_result {
if e.to_string().contains("fencing token is not initialized") {
continue; // next block; not fatal
}
return Err(e);
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Lost lock during repair — another leader took over
- Cannot publish block because fencing token is not initialize
- Cannot reconcile: only {}/{} Redis nodes responded ({} faile
- Timed out reading stream entries from Redis node
- Backlog unresolved at height {current_height}: stream indica
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/21f243438f619626.
Report an issue: GitHub.