diff --git a/.changelog/evil-pigs-cry.md b/.changelog/evil-pigs-cry.md new file mode 100644 index 0000000000..69618c8fba --- /dev/null +++ b/.changelog/evil-pigs-cry.md @@ -0,0 +1,6 @@ +--- +reth-engine-tree: patch +reth-trie-sparse-parallel: patch +--- + +Added tracing spans and debug logs to sparse trie operations for better observability during parallel state root computation. diff --git a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs index 6942d2569d..8c37e296fe 100644 --- a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs +++ b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs @@ -623,6 +623,7 @@ where if new { &mut self.new_storage_updates } else { &mut self.storage_updates }; // Process all storage updates in parallel, skipping tries with no pending updates. + let span = tracing::Span::current(); let storage_results = storage_updates .iter_mut() .filter(|(_, updates)| !updates.is_empty()) @@ -634,6 +635,7 @@ where }) .par_bridge_buffered() .map(|(address, updates, mut fetched, mut trie)| { + let _enter = debug_span!(target: "engine::tree::payload_processor::sparse_trie", parent: &span, "storage trie leaf updates", ?address).entered(); let mut targets = Vec::new(); trie.update_leaves(updates, |path, min_len| match fetched.entry(path) { @@ -653,6 +655,8 @@ where }) .collect::, _>>()?; + drop(span); + for (address, targets, fetched, trie) in storage_results { self.fetched_storage_targets.insert(*address, fetched); self.trie.insert_storage_trie(*address, trie); @@ -714,6 +718,7 @@ where return Ok(()); } + let span = tracing::Span::current(); let roots = self .trie .storage_tries_mut() @@ -722,6 +727,7 @@ where self.storage_updates.get(*address).is_some_and(|updates| updates.is_empty()) }) .map(|(address, trie)| { + let _enter = debug_span!(target: "engine::tree::payload_processor::sparse_trie", parent: &span, "storage root", ?address).entered(); let root = trie.root().expect("updates are drained, trie should be revealed by now"); diff --git a/crates/trie/sparse-parallel/src/trie.rs b/crates/trie/sparse-parallel/src/trie.rs index 9a23e45e38..499f58bebe 100644 --- a/crates/trie/sparse-parallel/src/trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -259,6 +259,10 @@ impl SparseTrie for ParallelSparseTrie { // Reveal lower subtrie nodes in parallel { use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; + use tracing::Span; + + // Capture the current span so it can be propagated to rayon worker threads + let parent_span = Span::current(); // Capture reference to upper subtrie nodes for boundary leaf reachability checks let upper_nodes = &self.upper_subtrie.nodes; @@ -309,6 +313,10 @@ impl SparseTrie for ParallelSparseTrie { .into_par_iter() .zip(node_groups.into_par_iter()) .map(|((subtrie_idx, mut subtrie), nodes)| { + // Enter the parent span to propagate context (e.g., hashed_address for storage + // tries) to the worker thread + let _guard = parent_span.enter(); + // reserve space in the HashMap ahead of time; doing it on a node-by-node basis // can cause multiple re-allocations as the hashmap grows. subtrie.nodes.reserve(nodes.len()); @@ -360,6 +368,13 @@ impl SparseTrie for ParallelSparseTrie { value: Vec, provider: P, ) -> SparseTrieResult<()> { + trace!( + target: "trie::parallel_sparse", + ?full_path, + value_len = value.len(), + "Updating leaf", + ); + // Check if the value already exists - if so, just update it (no structural changes needed) if self.upper_subtrie.inner.values.contains_key(&full_path) { self.prefix_set.insert(full_path); @@ -611,6 +626,12 @@ impl SparseTrie for ParallelSparseTrie { full_path: &Nibbles, provider: P, ) -> SparseTrieResult<()> { + trace!( + target: "trie::parallel_sparse", + ?full_path, + "Removing leaf", + ); + // When removing a leaf node it's possibly necessary to modify its parent node, and possibly // the parent's parent node. It is not ever necessary to descend further than that; once an // extension node is hit it must terminate in a branch or the root, which won't need further @@ -2787,6 +2808,14 @@ impl SparseSubtrie { return Ok(false) } + trace!( + target: "trie::parallel_sparse", + ?path, + ?node, + ?masks, + "Revealing node", + ); + match node { TrieNode::EmptyRoot => { // For an empty root, ensure that we are at the root path, and at the upper subtrie. @@ -3147,6 +3176,14 @@ impl SparseSubtrieInner { self.buffers.rlp_buf.clear(); let rlp_node = LeafNodeRef { key, value }.rlp(&mut self.buffers.rlp_buf); *hash = rlp_node.as_hash(); + trace!( + target: "trie::parallel_sparse", + ?path, + ?key, + value = %alloy_primitives::hex::encode(value), + ?hash, + "Calculated leaf hash", + ); (rlp_node, SparseNodeType::Leaf) } }