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

publish setup failed: missing redis node at index {idx}

Error message

publish setup failed: missing redis node at index {idx}

What it means

Invariant violation during fan-out setup in publish_block_on_all_nodes: the code indexes self.redis_nodes while preparing per-node write tasks, but the node list no longer contains an entry at the requested index (e.g. the list was empty, truncated, or mutated between length computation and task construction). It is a pre-flight guard that aborts the publish before any Redis write is attempted; no block data has been sent when it fires.

Source

Thrown at crates/fuel-core/src/service/adapters/consensus_module/poa.rs:1157

        let n = self.redis_nodes.len();
        let mut results: Vec<Option<anyhow::Result<WriteBlockResult>>> =
            (0..n).map(|_| None).collect();
        let mut written_count = 0usize;
        let quorum_start = std::time::Instant::now();

        // Share the block payload across spawned tasks via `Arc` so we
        // clone it once rather than once per node.
        let block: std::sync::Arc<SealedBlock> = std::sync::Arc::new(block.clone());
        let block_data: std::sync::Arc<[u8]> = block_data.into();

        let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<(
            usize,
            anyhow::Result<WriteBlockResult>,
        )>();
        #[allow(clippy::needless_range_loop)]
        for idx in 0..n {
            let Some(per_node) = self.per_node_adapter(idx) else {
                results[idx] = Some(Err(anyhow!(
                    "publish setup failed: missing redis node at index {idx}"
                )));
                continue;
            };
            let tx = tx.clone();
            let block = block.clone();
            let block_data = block_data.clone();
            // Increment the gauge synchronously here (before the task
            // is queued) and move the guard into the closure so it
            // decrements whether the task completes normally, panics,
            // or is cancelled mid-await.
            let task_guard = OutstandingPublishTaskGuard::new();
            tokio::spawn(async move {
                let _task_guard = task_guard;
                let result = per_node
                    .publish_block_on_node(epoch, &block, &block_data)
                    .await;
                // Ignored if the receiver was dropped after quorum — the

View on GitHub (pinned to add100d30d)

Solutions

  1. Verify the PoA consensus module's redis_nodes configuration contains the expected number of entries before starting the publisher
  2. Rebuild/refresh the node list from configuration at the start of each publish cycle instead of caching a stale index
  3. Log the list length alongside the missing index to distinguish an empty list from an off-by-one index bug
  4. Fail the publish attempt explicitly so the caller (publish_produced_block / repair_sub_quorum_block) can retry or report the misconfiguration
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at crates/fuel-core/src/service/adapters/consensus_module/poa.rs:1157 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of FuelLabs/fuel-core@add100d30d (2026-09-05). Data as JSON: /api/errors/cd8bf2bee2e5d17d. Report an issue: GitHub.