From 53f922927a79047ee5b051fe091b79be4ef9f805 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Tue, 3 Feb 2026 03:48:54 +0400 Subject: [PATCH] feat: reintroduce `--engine.state-root-task-compare-updates` (#21717) --- crates/engine/tree/src/tree/mod.rs | 1 - .../engine/tree/src/tree/payload_validator.rs | 64 +++++++++++++++++++ crates/engine/tree/src/tree/trie_updates.rs | 5 +- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 0b16551190..a90aea7d65 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -61,7 +61,6 @@ mod persistence_state; pub mod precompile_cache; #[cfg(test)] mod tests; -#[expect(unused)] mod trie_updates; use crate::tree::error::AdvancePersistenceError; diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index e559ee58af..ca6119028d 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -520,6 +520,14 @@ where info!(target: "engine::tree::payload_validator", ?state_root, ?elapsed, "State root task finished"); // we double check the state root here for good measure if state_root == block.header().state_root() { + // Compare trie updates with serial computation if configured + if self.config.always_compare_trie_updates() { + self.compare_trie_updates_with_serial( + overlay_factory.clone(), + &hashed_state, + trie_updates.clone(), + ); + } maybe_state_root = Some((state_root, trie_updates, elapsed)) } else { warn!( @@ -895,6 +903,62 @@ where .root_with_updates()?) } + /// Compares trie updates from the state root task with serial state root computation. + /// + /// This is used for debugging and validating the correctness of the parallel state root + /// task implementation. When enabled via `--engine.state-root-task-compare-updates`, this + /// method runs a separate serial state root computation and compares the resulting trie + /// updates. + fn compare_trie_updates_with_serial( + &self, + overlay_factory: OverlayStateProviderFactory

, + hashed_state: &HashedPostState, + task_trie_updates: TrieUpdates, + ) { + debug!(target: "engine::tree::payload_validator", "Comparing trie updates with serial computation"); + + match self.compute_state_root_serial(overlay_factory.clone(), hashed_state) { + Ok((serial_root, serial_trie_updates)) => { + debug!( + target: "engine::tree::payload_validator", + ?serial_root, + "Serial state root computation finished for comparison" + ); + + // Get a database provider to use as trie cursor factory + match overlay_factory.database_provider_ro() { + Ok(provider) => { + if let Err(err) = super::trie_updates::compare_trie_updates( + &provider, + task_trie_updates, + serial_trie_updates, + ) { + warn!( + target: "engine::tree::payload_validator", + %err, + "Error comparing trie updates" + ); + } + } + Err(err) => { + warn!( + target: "engine::tree::payload_validator", + %err, + "Failed to get database provider for trie update comparison" + ); + } + } + } + Err(err) => { + warn!( + target: "engine::tree::payload_validator", + %err, + "Failed to compute serial state root for comparison" + ); + } + } + } + /// Validates the block after execution. /// /// This performs: diff --git a/crates/engine/tree/src/tree/trie_updates.rs b/crates/engine/tree/src/tree/trie_updates.rs index ba8f7fc16a..c81faecfd0 100644 --- a/crates/engine/tree/src/tree/trie_updates.rs +++ b/crates/engine/tree/src/tree/trie_updates.rs @@ -98,7 +98,7 @@ impl StorageTrieUpdatesDiff { /// Compares the trie updates from state root task, regular state root calculation and database, /// and logs the differences if there's any. -pub(super) fn compare_trie_updates( +pub(crate) fn compare_trie_updates( trie_cursor_factory: impl TrieCursorFactory, task: TrieUpdates, regular: TrieUpdates, @@ -186,7 +186,8 @@ fn compare_storage_trie_updates( task: &mut StorageTrieUpdates, regular: &mut StorageTrieUpdates, ) -> Result { - let database_not_exists = trie_cursor()?.next()?.is_none(); + // Check if the storage trie exists by seeking to the first entry + let database_not_exists = trie_cursor()?.seek(Nibbles::default())?.is_none(); let mut diff = StorageTrieUpdatesDiff { // If the deletion is a no-op, meaning that the entry is not in the // database, do not add it to the diff.