From 40c431a17dd229c6b1c36ee62bc5c3f671ee4c86 Mon Sep 17 00:00:00 2001 From: owen Date: Thu, 10 Sep 2026 19:02:10 +0100 Subject: [PATCH] Treat a missing parent state as a temporary sim failure The simulator returns "no state found for block " when its node knows the parent header but does not have that block on its head chain. A late parent, a reorg, or a lagging node causes this. `is_temporary` did not match the text, so the relay demoted the builder for a sim-side condition. Match the reth text and the builder's own "parent state is not available". Co-Authored-By: Claude Opus 5 (1M context) --- crates/builder/src/validation/error.rs | 1 + crates/common/src/simulator.rs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/crates/builder/src/validation/error.rs b/crates/builder/src/validation/error.rs index 04642dd7..5f5b311a 100644 --- a/crates/builder/src/validation/error.rs +++ b/crates/builder/src/validation/error.rs @@ -30,6 +30,7 @@ pub enum ValidationError { PostExecution(String), #[error("block state root mismatch: got {got}, expected {expected}")] StateRootMismatch { got: B256, expected: B256 }, + // Text matched by `BlockSimError::is_temporary`; changing it demotes builders. #[error("parent state is not available")] MissingParentState, #[error("could not verify proposer payment")] diff --git a/crates/common/src/simulator.rs b/crates/common/src/simulator.rs index f446b20e..def506af 100644 --- a/crates/common/src/simulator.rs +++ b/crates/common/src/simulator.rs @@ -64,6 +64,8 @@ const BLOCK_ALREADY_KNOWN: &str = "block already known"; const BLOCK_TOO_OLD: &str = "block is too old, outside validation window"; const BLOCK_REQ_REORG: &str = "block requires a reorg"; const PARENT_BLOCK_NOT_FOUND: &str = "could not find parent block: parent block not found"; +const NO_STATE_FOR_BLOCK: &str = "no state found for block"; +const MISSING_PARENT_STATE: &str = "parent state is not available"; #[derive(Debug, Clone, Error)] pub enum BlockSimError { @@ -110,7 +112,9 @@ impl BlockSimError { PARENT_NOT_FOUND => true, PARENT_BLOCK_NOT_FOUND => true, BLOCK_REQ_REORG => true, + MISSING_PARENT_STATE => true, r if r.starts_with(MISSING_TRIE_NODE) => true, + r if r.starts_with(NO_STATE_FOR_BLOCK) => true, _ => false, }, BlockSimError::Timeout => true, @@ -238,6 +242,20 @@ mod tests { ) } + /// The parent state can be missing while the simulator lags or reorgs, so it must not demote. + #[test] + fn missing_parent_state_never_demotes() { + for reason in [ + "no state found for block 0xf9d64b1e6815113d8a761cac2d094802520a082b19221dcbeff2940b469adb7d", + "parent state is not available", + ] { + let err = BlockSimError::BlockValidationFailed(reason.to_string()); + + assert!(err.is_temporary(), "{err}"); + assert!(!err.is_demotable(), "{err}"); + } + } + /// A relay-side hydration failure is never the builder's fault, so it must not demote. #[test] fn relay_hydration_failure_never_demotes() {