fix(trie): Guard against infinite loop in proof_v2 (#21789)

This commit is contained in:
Brian Picciano
2026-02-04 11:57:36 +01:00
committed by GitHub
parent 89be91de0e
commit 32c08b7ddb
3 changed files with 33 additions and 2 deletions

View File

@@ -57,6 +57,12 @@ pub enum StateProofError {
/// RLP decoding error.
#[error(transparent)]
Rlp(#[from] alloy_rlp::Error),
/// Trie inconsistency detected during proof calculation.
///
/// This occurs when cached trie nodes disagree with the leaf data, causing
/// proof calculation to be unable to make forward progress.
#[error("trie inconsistency: {0}")]
TrieInconsistency(alloc::string::String),
}
impl From<StateProofError> for ProviderError {
@@ -64,6 +70,7 @@ impl From<StateProofError> for ProviderError {
match value {
StateProofError::Database(error) => Self::Database(error),
StateProofError::Rlp(error) => Self::Rlp(error),
StateProofError::TrieInconsistency(msg) => Self::Database(DatabaseError::Other(msg)),
}
}
}

View File

@@ -264,6 +264,9 @@ impl From<StateProofError> for ParallelStateRootError {
match error {
StateProofError::Database(err) => Self::Provider(ProviderError::Database(err)),
StateProofError::Rlp(err) => Self::Provider(ProviderError::Rlp(err)),
StateProofError::TrieInconsistency(msg) => {
Self::Provider(ProviderError::TrieWitnessError(msg))
}
}
}
}

View File

@@ -17,7 +17,7 @@ use alloy_trie::{BranchNodeCompact, TrieMask};
use reth_execution_errors::trie::StateProofError;
use reth_trie_common::{BranchNode, BranchNodeMasks, Nibbles, ProofTrieNode, RlpNode, TrieNode};
use std::cmp::Ordering;
use tracing::{instrument, trace};
use tracing::{error, instrument, trace};
mod value;
pub use value::*;
@@ -1159,13 +1159,16 @@ where
trace!(target: TRACE_TARGET, "Starting loop");
loop {
// Save the previous lower bound to detect forward progress.
let prev_uncalculated_lower_bound = uncalculated_lower_bound;
// Determine the range of keys of the overall trie which need to be re-computed.
let Some((calc_lower_bound, calc_upper_bound)) = self.next_uncached_key_range(
&mut targets,
trie_cursor_state,
&sub_trie_targets.prefix,
sub_trie_upper_bound.as_ref(),
uncalculated_lower_bound,
prev_uncalculated_lower_bound,
)?
else {
// If `next_uncached_key_range` determines that there can be no more keys then
@@ -1173,6 +1176,24 @@ where
break;
};
// Forward-progress guard: detect trie inconsistencies that would cause infinite loops.
// If `next_uncached_key_range` returns a range that starts before the previous
// lower bound, we've gone backwards and would loop forever.
//
// This can specifically happen when there is a cached branch which shouldn't exist, or
// if state mask bit is set on a cached branch which shouldn't be.
if let Some(prev_lower) = prev_uncalculated_lower_bound.as_ref() &&
calc_lower_bound < *prev_lower
{
let msg = format!(
"next_uncached_key_range went backwards: calc_lower={calc_lower_bound:?} < \
prev_lower={prev_lower:?}, calc_upper={calc_upper_bound:?}, prefix={:?}",
sub_trie_targets.prefix,
);
error!(target: TRACE_TARGET, "{msg}");
return Err(StateProofError::TrieInconsistency(msg));
}
// Calculate the trie for that range of keys
self.calculate_key_range(
value_encoder,