{"record":{"id":"6c40ad13bc9120d6","repo":"FuelLabs/fuel-core","slug":"publish-abandoned-quorum-reached-before-this-node","errorCode":null,"errorMessage":"publish abandoned: quorum reached before this node responded","messagePattern":"publish abandoned: quorum reached before this node responded","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"info","filePath":"crates/fuel-core/src/service/adapters/consensus_module/poa.rs","lineNumber":1080,"sourceCode":"            if matches!(result, Ok(WriteBlockResult::Written)) {\n                written_count = written_count.saturating_add(1);\n            }\n            results[idx] = Some(result);\n            received = received.saturating_add(1);\n\n            if self.quorum_reached(written_count) {\n                // Quorum reached. Return immediately; any threads still\n                // running are abandoned. Their later `tx.send` will fail\n                // silently because we drop the receiver below.\n                break;\n            }\n        }\n\n        results\n            .into_iter()\n            .map(|r| {\n                r.unwrap_or_else(|| {\n                    Err(anyhow!(\n                        \"publish abandoned: quorum reached before this \\\n                         node responded\"\n                    ))\n                })\n            })\n            .collect()\n    }\n\n    /// `'static` helper that runs `write_block.lua` against a single node.\n    /// Free function (no `&self`) so detached threads spawned by\n    /// `publish_block_on_all_nodes` can run it without borrowing the adapter.\n    #[allow(clippy::too_many_arguments)]\n    fn invoke_write_block_script(\n        redis_client: &redis::Client,\n        node_timeout: Duration,\n        block_stream_key: &str,\n        epoch_key: &str,\n        lease_key: &str,","sourceCodeStart":1062,"sourceCodeEnd":1098,"githubUrl":"https://github.com/FuelLabs/fuel-core/blob/b9d4d170da3a31c9ace5f963d633b326348e0d42/crates/fuel-core/src/service/adapters/consensus_module/poa.rs#L1062-L1098","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet all_ok = results.into_iter().all(|r| r.is_ok()); // misreads abandoned as failure\n\n// after — only Written counts, abandoned slots are ignored\nlet written = results\n    .into_iter()\n    .filter(|r| matches!(r, Ok(WriteBlockResult::Written)))\n    .count();\nlet ok = self.quorum_reached(written);","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Filter the benign 'abandoned' placeholder out before aggregating results.\nlet meaningful: Vec<_> = results.into_iter().filter(|r| {\n    !matches!(r, Err(ref e) if e.to_string().contains(\"publish abandoned\"))\n}).collect();","preventionTips":["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."],"tags":["redis","quorum","fan-out","best-effort"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}