feat(trie): add trie-debug feature for recording sparse trie mutations (#22234)

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Brian Picciano
2026-02-17 12:59:11 +01:00
committed by GitHub
parent 117b212e2e
commit 8db352dfd2
14 changed files with 388 additions and 7 deletions

View File

@@ -73,6 +73,7 @@ reth-prune-types = { workspace = true, optional = true }
reth-stages = { workspace = true, optional = true }
reth-static-file = { workspace = true, optional = true }
reth-tracing = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }
[dev-dependencies]
# reth
@@ -143,6 +144,7 @@ test-utils = [
"reth-evm-ethereum/test-utils",
"reth-tasks/test-utils",
]
trie-debug = ["reth-trie-sparse/trie-debug", "dep:serde_json"]
rocksdb = [
"reth-provider/rocksdb",
"reth-prune/rocksdb",

View File

@@ -25,6 +25,8 @@ use reth_trie_parallel::{
root::ParallelStateRootError,
targets_v2::MultiProofTargetsV2,
};
#[cfg(feature = "trie-debug")]
use reth_trie_sparse::debug_recorder::TrieDebugRecorder;
use reth_trie_sparse::{
errors::{SparseStateTrieResult, SparseTrieErrorKind, SparseTrieResult},
provider::{TrieNodeProvider, TrieNodeProviderFactory},
@@ -183,11 +185,19 @@ where
ParallelStateRootError::Other(format!("could not calculate state root: {e:?}"))
})?;
#[cfg(feature = "trie-debug")]
let debug_recorders = self.trie.take_debug_recorders();
let end = Instant::now();
self.metrics.sparse_trie_final_update_duration_histogram.record(end.duration_since(start));
self.metrics.sparse_trie_total_duration_histogram.record(end.duration_since(now));
Ok(StateRootComputeOutcome { state_root, trie_updates })
Ok(StateRootComputeOutcome {
state_root,
trie_updates,
#[cfg(feature = "trie-debug")]
debug_recorders,
})
}
/// Clears and shrinks the trie, discarding all state.
@@ -475,11 +485,19 @@ where
ParallelStateRootError::Other(format!("could not calculate state root: {e:?}"))
})?;
#[cfg(feature = "trie-debug")]
let debug_recorders = self.trie.take_debug_recorders();
let end = Instant::now();
self.metrics.sparse_trie_final_update_duration_histogram.record(end.duration_since(start));
self.metrics.sparse_trie_total_duration_histogram.record(end.duration_since(now));
Ok(StateRootComputeOutcome { state_root, trie_updates })
Ok(StateRootComputeOutcome {
state_root,
trie_updates,
#[cfg(feature = "trie-debug")]
debug_recorders,
})
}
/// Processes a [`SparseTrieTaskMessage`] from the hashing task.
@@ -891,6 +909,10 @@ pub struct StateRootComputeOutcome {
pub state_root: B256,
/// The trie updates.
pub trie_updates: TrieUpdates,
/// Debug recorders taken from the sparse tries, keyed by `None` for account trie
/// and `Some(address)` for storage tries.
#[cfg(feature = "trie-debug")]
pub debug_recorders: Vec<(Option<B256>, TrieDebugRecorder)>,
}
/// Updates the sparse trie with the given proofs and state, and returns the elapsed time.

View File

@@ -15,6 +15,8 @@ use alloy_eip7928::BlockAccessList;
use alloy_eips::{eip1898::BlockWithParent, eip4895::Withdrawal, NumHash};
use alloy_evm::Evm;
use alloy_primitives::B256;
#[cfg(feature = "trie-debug")]
use reth_trie_sparse::debug_recorder::TrieDebugRecorder;
use crate::tree::payload_processor::receipt_root_task::{IndexedReceipt, ReceiptRootTaskHandle};
use reth_chain_state::{CanonicalInMemoryState, DeferredTrieData, ExecutedBlock, LazyOverlay};
@@ -532,6 +534,8 @@ where
let root_time = Instant::now();
let mut maybe_state_root = None;
let mut state_root_task_failed = false;
#[cfg(feature = "trie-debug")]
let mut trie_debug_recorders = Vec::new();
match strategy {
StateRootStrategy::StateRootTask => {
@@ -547,17 +551,34 @@ where
);
match task_result {
Ok(StateRootComputeOutcome { state_root, trie_updates }) => {
Ok(StateRootComputeOutcome {
state_root,
trie_updates,
#[cfg(feature = "trie-debug")]
debug_recorders,
}) => {
let elapsed = root_time.elapsed();
info!(target: "engine::tree::payload_validator", ?state_root, ?elapsed, "State root task finished");
#[cfg(feature = "trie-debug")]
{
trie_debug_recorders = debug_recorders;
}
// Compare trie updates with serial computation if configured
if self.config.always_compare_trie_updates() {
self.compare_trie_updates_with_serial(
let _has_diff = self.compare_trie_updates_with_serial(
overlay_factory.clone(),
&hashed_state,
trie_updates.clone(),
);
#[cfg(feature = "trie-debug")]
if _has_diff {
Self::write_trie_debug_recorders(
block.header().number(),
&trie_debug_recorders,
);
}
}
// we double check the state root here for good measure
@@ -570,6 +591,11 @@ where
block_state_root = ?block.header().state_root(),
"State root task returned incorrect state root"
);
#[cfg(feature = "trie-debug")]
Self::write_trie_debug_recorders(
block.header().number(),
&trie_debug_recorders,
);
state_root_task_failed = true;
}
}
@@ -635,6 +661,9 @@ where
// ensure state root matches
if state_root != block.header().state_root() {
#[cfg(feature = "trie-debug")]
Self::write_trie_debug_recorders(block.header().number(), &trie_debug_recorders);
// call post-block hook
self.on_invalid_block(
&parent_block,
@@ -1007,7 +1036,12 @@ where
))
})?;
let (state_root, trie_updates) = result?;
return Ok(Ok(StateRootComputeOutcome { state_root, trie_updates }));
return Ok(Ok(StateRootComputeOutcome {
state_root,
trie_updates,
#[cfg(feature = "trie-debug")]
debug_recorders: Vec::new(),
}));
}
Err(RecvTimeoutError::Timeout) => {}
}
@@ -1019,7 +1053,12 @@ where
"State root timeout race won"
);
let (state_root, trie_updates) = result?;
return Ok(Ok(StateRootComputeOutcome { state_root, trie_updates }));
return Ok(Ok(StateRootComputeOutcome {
state_root,
trie_updates,
#[cfg(feature = "trie-debug")]
debug_recorders: Vec::new(),
}));
}
}
}
@@ -1037,7 +1076,7 @@ where
overlay_factory: OverlayStateProviderFactory<P>,
hashed_state: &HashedPostState,
task_trie_updates: TrieUpdates,
) {
) -> bool {
debug!(target: "engine::tree::payload_validator", "Comparing trie updates with serial computation");
match Self::compute_state_root_serial(overlay_factory.clone(), hashed_state) {
@@ -1061,6 +1100,7 @@ where
%err,
"Error comparing trie updates"
);
return true;
}
}
Err(err) => {
@@ -1080,6 +1120,45 @@ where
);
}
}
false
}
/// Writes trie debug recorders to a JSON file for the given block number.
///
/// The file is written to the current working directory as
/// `trie_debug_block_{block_number}.json`.
#[cfg(feature = "trie-debug")]
fn write_trie_debug_recorders(
block_number: u64,
recorders: &[(Option<B256>, TrieDebugRecorder)],
) {
let path = format!("trie_debug_block_{block_number}.json");
match serde_json::to_string_pretty(recorders) {
Ok(json) => match std::fs::write(&path, json) {
Ok(()) => {
warn!(
target: "engine::tree::payload_validator",
%path,
"Wrote trie debug recorders to file"
);
}
Err(err) => {
warn!(
target: "engine::tree::payload_validator",
%err,
%path,
"Failed to write trie debug recorders"
);
}
},
Err(err) => {
warn!(
target: "engine::tree::payload_validator",
%err,
"Failed to serialize trie debug recorders"
);
}
}
}
/// Validates the block after execution.