mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-02-19 03:04:27 -05:00
fix(trie): Guard against infinite loop in proof_v2 (#21789)
This commit is contained in:
@@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user