fix(engine): only warn for critical capability mismatches (#21398)

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Matthias Seitz
2026-01-26 13:36:49 +01:00
committed by GitHub
parent 4baf2baec4
commit 6d19c0ed8e

View File

@@ -3,6 +3,13 @@
use std::collections::HashSet;
use tracing::warn;
/// Critical Engine API method prefixes that warrant warnings on capability mismatches.
///
/// These are essential for block production and chain synchronization. Missing support
/// for these methods indicates a significant version mismatch that operators should address.
const CRITICAL_METHOD_PREFIXES: &[&str] =
&["engine_forkchoiceUpdated", "engine_getPayload", "engine_newPayload"];
/// All Engine API capabilities supported by Reth (Ethereum mainnet).
///
/// See <https://github.com/ethereum/execution-apis/tree/main/src/engine> for updates.
@@ -72,31 +79,52 @@ impl EngineCapabilities {
CapabilityMismatches { missing_in_el, missing_in_cl }
}
/// Logs warnings if CL and EL capabilities don't match.
/// Logs warnings if CL and EL capabilities don't match for critical methods.
///
/// Called during `engine_exchangeCapabilities` to warn operators about
/// version mismatches between the consensus layer and execution layer.
///
/// Only warns about critical methods (`engine_forkchoiceUpdated`, `engine_getPayload`,
/// `engine_newPayload`) that are essential for block production and chain synchronization.
/// Non-critical methods like `engine_getBlobs` are not warned about since not all
/// clients support them.
pub fn log_capability_mismatches(&self, cl_capabilities: &[String]) {
let mismatches = self.get_capability_mismatches(cl_capabilities);
if !mismatches.missing_in_el.is_empty() {
let critical_missing_in_el: Vec<_> =
mismatches.missing_in_el.iter().filter(|m| is_critical_method(m)).cloned().collect();
let critical_missing_in_cl: Vec<_> =
mismatches.missing_in_cl.iter().filter(|m| is_critical_method(m)).cloned().collect();
if !critical_missing_in_el.is_empty() {
warn!(
target: "rpc::engine",
missing = ?mismatches.missing_in_el,
missing = ?critical_missing_in_el,
"CL supports Engine API methods that Reth doesn't. Consider upgrading Reth."
);
}
if !mismatches.missing_in_cl.is_empty() {
if !critical_missing_in_cl.is_empty() {
warn!(
target: "rpc::engine",
missing = ?mismatches.missing_in_cl,
missing = ?critical_missing_in_cl,
"Reth supports Engine API methods that CL doesn't. Consider upgrading your consensus client."
);
}
}
}
/// Returns `true` if the method is critical for block production and chain synchronization.
fn is_critical_method(method: &str) -> bool {
CRITICAL_METHOD_PREFIXES.iter().any(|prefix| {
method.starts_with(prefix) &&
method[prefix.len()..]
.strip_prefix('V')
.is_some_and(|s| s.chars().next().is_some_and(|c| c.is_ascii_digit()))
})
}
impl Default for EngineCapabilities {
fn default() -> Self {
Self::new(CAPABILITIES.iter().copied())
@@ -173,4 +201,20 @@ mod tests {
assert_eq!(result.missing_in_el, vec!["a_other", "z_other"]);
assert_eq!(result.missing_in_cl, vec!["a_method", "z_method"]);
}
#[test]
fn test_is_critical_method() {
assert!(is_critical_method("engine_forkchoiceUpdatedV1"));
assert!(is_critical_method("engine_forkchoiceUpdatedV3"));
assert!(is_critical_method("engine_getPayloadV1"));
assert!(is_critical_method("engine_getPayloadV4"));
assert!(is_critical_method("engine_newPayloadV1"));
assert!(is_critical_method("engine_newPayloadV4"));
assert!(!is_critical_method("engine_getBlobsV1"));
assert!(!is_critical_method("engine_getBlobsV3"));
assert!(!is_critical_method("engine_getPayloadBodiesByHashV1"));
assert!(!is_critical_method("engine_getPayloadBodiesByRangeV1"));
assert!(!is_critical_method("engine_getClientVersionV1"));
}
}