FuelLabs/fuel-core · info · anyhow::Error
publish abandoned: quorum reached before this node responded
Error message
publish abandoned: quorum reached before this node responded
What it means
publish_block_on_all_nodes fans write_block.lua out to every Redis node and returns one result slot per node; when quorum of Written results arrives it breaks and drops the receiver, so slots for still-in-flight nodes stay None and are rendered as this placeholder error. It records 'result discarded because success was already assured', not a write failure — the caller already counted a quorum of successes.
Source
Thrown at crates/fuel-core/src/service/adapters/consensus_module/poa.rs:1080
if matches!(result, Ok(WriteBlockResult::Written)) {
written_count = written_count.saturating_add(1);
}
results[idx] = Some(result);
received = received.saturating_add(1);
if self.quorum_reached(written_count) {
// Quorum reached. Return immediately; any threads still
// running are abandoned. Their later `tx.send` will fail
// silently because we drop the receiver below.
break;
}
}
results
.into_iter()
.map(|r| {
r.unwrap_or_else(|| {
Err(anyhow!(
"publish abandoned: quorum reached before this \
node responded"
))
})
})
.collect()
}
/// `'static` helper that runs `write_block.lua` against a single node.
/// Free function (no `&self`) so detached threads spawned by
/// `publish_block_on_all_nodes` can run it without borrowing the adapter.
#[allow(clippy::too_many_arguments)]
fn invoke_write_block_script(
redis_client: &redis::Client,
node_timeout: Duration,
block_stream_key: &str,
epoch_key: &str,
lease_key: &str,View on GitHub (pinned to b9d4d170da)
Solutions
- In callers, treat this message as 'skipped', not failure: count only Ok(WriteBlockResult::Written) and compare against quorum, exactly as publish_produced_block (poa.rs:1379-1394) already does.
- If you need every node's outcome, remove the early break and await all senders instead of only quorum.
- Tune per-node Redis command timeouts so stragglers answer within the publish window.
Example fix
// before
let all_ok = results.into_iter().all(|r| r.is_ok()); // misreads abandoned as failure
// after — only Written counts, abandoned slots are ignored
let written = results
.into_iter()
.filter(|r| matches!(r, Ok(WriteBlockResult::Written)))
.count();
let ok = self.quorum_reached(written); Defensive patterns
Strategy: fallback
Try / catch
// Filter the benign 'abandoned' placeholder out before aggregating results.
let meaningful: Vec<_> = results.into_iter().filter(|r| {
!matches!(r, Err(ref e) if e.to_string().contains("publish abandoned"))
}).collect(); Prevention
- Never treat the aggregate result of publish_block_on_all_nodes as all-or-nothing; only Written counts matter.
- Set Redis command timeouts so stragglers answer within the publish window.
- Log at debug, not error, for abandoned slots — they are expected under quorum semantics.
When it happens
Trigger: A quorum of fast Redis nodes acknowledge the write while at least one slower node has not answered; its slot is None when the loop breaks, so the aggregate Vec contains this error for that node.
Common situations: One Redis endpoint with higher latency (cross-datacenter, overloaded, pause) during block publish or sub-quorum repair. Callers that propagate Err from the aggregate naively misread this benign case as a real failure.
Related errors
- Cannot reconcile: only {}/{} Redis nodes responded ({} faile
- Backlog unresolved at height {current_height}: repair failed
- Failed to release lease on quorum
- Failed to publish block to redis quorum with fencing checks
- At least one redis url is required for leader lock
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/6c40ad13bc9120d6.
Report an issue: GitHub.