@@ -21,9 +21,32 @@ struct ChainspecEvolveConfig {
2121 /// Block height at which the custom contract size limit activates.
2222 #[ serde( default , rename = "contractSizeLimitActivationHeight" ) ]
2323 pub contract_size_limit_activation_height : Option < u64 > ,
24- /// Block height at which canonical hash rewiring activates.
25- #[ serde( default , rename = "hashRewireActivationHeight" ) ]
26- pub hash_rewire_activation_height : Option < u64 > ,
24+ /// Block height at which canonical block hash enforcement activates.
25+ ///
26+ /// # Background
27+ ///
28+ /// Early versions of ev-node passed block hashes from height H-1 instead of H
29+ /// when communicating with ev-reth via the Engine API. This caused block hashes
30+ /// to not match the canonical Ethereum block hash (keccak256 of RLP-encoded header),
31+ /// resulting in block explorers like Blockscout incorrectly displaying every block
32+ /// as a fork due to parent hash mismatches.
33+ ///
34+ /// # Migration Strategy
35+ ///
36+ /// For existing networks with historical blocks containing non-canonical hashes:
37+ /// - Set this to a future block height where the fix will activate
38+ /// - Before the activation height: hash mismatches are bypassed (legacy mode)
39+ /// - At and after the activation height: canonical hash validation is enforced
40+ ///
41+ /// This allows nodes to sync from genesis on networks with historical hash
42+ /// mismatches while ensuring new blocks use correct canonical hashes.
43+ ///
44+ /// # Default Behavior
45+ ///
46+ /// If not set, canonical hash validation is enforced from genesis (block 0).
47+ /// This is the correct setting for new networks without legacy data.
48+ #[ serde( default , rename = "canonicalHashActivationHeight" ) ]
49+ pub canonical_hash_activation_height : Option < u64 > ,
2750}
2851
2952/// Configuration for the Evolve payload builder
@@ -47,17 +70,32 @@ pub struct EvolvePayloadBuilderConfig {
4770 /// Block height at which the custom contract size limit activates.
4871 #[ serde( default ) ]
4972 pub contract_size_limit_activation_height : Option < u64 > ,
50- /// Block height at which canonical hash rewiring activates.
73+ /// Block height at which canonical block hash enforcement activates.
74+ ///
75+ /// # Background
76+ ///
77+ /// Early versions of ev-node passed block hashes from height H-1 instead of H
78+ /// when communicating with ev-reth via the Engine API. This caused block hashes
79+ /// to not match the canonical Ethereum block hash (keccak256 of RLP-encoded header),
80+ /// resulting in block explorers like Blockscout incorrectly displaying every block
81+ /// as a fork due to parent hash mismatches.
82+ ///
83+ /// # Migration Strategy
84+ ///
85+ /// For existing networks with historical blocks containing non-canonical hashes:
86+ /// - Set this to a future block height where the fix will activate
87+ /// - Before the activation height: hash mismatches are bypassed (legacy mode)
88+ /// - At and after the activation height: canonical hash validation is enforced
89+ ///
90+ /// This allows nodes to sync from genesis on networks with historical hash
91+ /// mismatches while ensuring new blocks use correct canonical hashes.
5192 ///
52- /// Before activation, ev-reth operates in "legacy" mode to support deployments that flowed an
53- /// application-level hash through Engine API payloads. In that mode the node may bypass block
54- /// hash mismatches and re-seal blocks with an alternate hash for compatibility.
93+ /// # Default Behavior
5594 ///
56- /// After activation, the node enforces canonical keccak block hashes as expected by standard
57- /// Ethereum tooling. Note that `header.state_root` always follows Ethereum semantics (the
58- /// current block's post-state root), independent of this setting.
95+ /// If not set, canonical hash validation is enforced from genesis (block 0).
96+ /// This is the correct setting for new networks without legacy data.
5997 #[ serde( default ) ]
60- pub hash_rewire_activation_height : Option < u64 > ,
98+ pub canonical_hash_activation_height : Option < u64 > ,
6199}
62100
63101impl EvolvePayloadBuilderConfig {
@@ -70,7 +108,7 @@ impl EvolvePayloadBuilderConfig {
70108 mint_precompile_activation_height : None ,
71109 contract_size_limit : None ,
72110 contract_size_limit_activation_height : None ,
73- hash_rewire_activation_height : None ,
111+ canonical_hash_activation_height : None ,
74112 }
75113 }
76114
@@ -105,7 +143,7 @@ impl EvolvePayloadBuilderConfig {
105143 config. contract_size_limit = extras. contract_size_limit ;
106144 config. contract_size_limit_activation_height =
107145 extras. contract_size_limit_activation_height ;
108- config. hash_rewire_activation_height = extras. hash_rewire_activation_height ;
146+ config. canonical_hash_activation_height = extras. canonical_hash_activation_height ;
109147 }
110148 Ok ( config)
111149 }
@@ -160,14 +198,27 @@ impl EvolvePayloadBuilderConfig {
160198 . and_then ( |( sink, activation) | ( block_number >= activation) . then_some ( sink) )
161199 }
162200
163- /// Returns the configured hash rewire activation height if present.
164- pub const fn hash_rewire_settings ( & self ) -> Option < u64 > {
165- self . hash_rewire_activation_height
166- }
167-
168- /// Returns true if the canonical hash rewiring should be active for the provided block.
169- pub const fn is_hash_rewire_active_for_block ( & self , block_number : u64 ) -> bool {
170- matches ! ( self . hash_rewire_activation_height, Some ( activation) if block_number >= activation)
201+ /// Returns true if canonical block hash validation should be enforced for the given block.
202+ ///
203+ /// This method controls whether the validator should reject blocks with hash mismatches
204+ /// or bypass the check for legacy compatibility.
205+ ///
206+ /// # Returns
207+ ///
208+ /// - `true`: Enforce canonical hash validation (reject mismatches)
209+ /// - `false`: Bypass hash validation (legacy mode for historical blocks)
210+ ///
211+ /// # Logic
212+ ///
213+ /// - If `canonical_hash_activation_height` is `None`: Always enforce (new networks)
214+ /// - If `canonical_hash_activation_height` is `Some(N)`:
215+ /// - `block_number < N`: Don't enforce (legacy mode)
216+ /// - `block_number >= N`: Enforce (canonical mode)
217+ pub const fn is_canonical_hash_enforced ( & self , block_number : u64 ) -> bool {
218+ match self . canonical_hash_activation_height {
219+ Some ( activation) => block_number >= activation,
220+ None => true , // Default: enforce canonical hashes from genesis
221+ }
171222 }
172223}
173224
@@ -247,8 +298,7 @@ mod tests {
247298 "baseFeeSink" : sink,
248299 "baseFeeRedirectActivationHeight" : 42 ,
249300 "mintAdmin" : admin,
250- "mintPrecompileActivationHeight" : 64 ,
251- "hashRewireActivationHeight" : 128
301+ "mintPrecompileActivationHeight" : 64
252302 } ) ;
253303
254304 let chainspec = create_test_chainspec_with_extras ( Some ( extras) ) ;
@@ -258,7 +308,6 @@ mod tests {
258308 assert_eq ! ( config. base_fee_redirect_activation_height, Some ( 42 ) ) ;
259309 assert_eq ! ( config. mint_admin, Some ( admin) ) ;
260310 assert_eq ! ( config. mint_precompile_activation_height, Some ( 64 ) ) ;
261- assert_eq ! ( config. hash_rewire_activation_height, Some ( 128 ) ) ;
262311 }
263312
264313 #[ test]
@@ -284,7 +333,6 @@ mod tests {
284333
285334 assert_eq ! ( config. base_fee_sink, None ) ;
286335 assert_eq ! ( config. base_fee_redirect_activation_height, None ) ;
287- assert_eq ! ( config. hash_rewire_activation_height, None ) ;
288336 }
289337
290338 #[ test]
@@ -297,7 +345,6 @@ mod tests {
297345 assert_eq ! ( config. mint_admin, None ) ;
298346 assert_eq ! ( config. base_fee_redirect_activation_height, None ) ;
299347 assert_eq ! ( config. mint_precompile_activation_height, None ) ;
300- assert_eq ! ( config. hash_rewire_activation_height, None ) ;
301348 }
302349
303350 #[ test]
@@ -336,7 +383,6 @@ mod tests {
336383 assert_eq ! ( config. mint_admin, None ) ;
337384 assert_eq ! ( config. base_fee_redirect_activation_height, None ) ;
338385 assert_eq ! ( config. mint_precompile_activation_height, None ) ;
339- assert_eq ! ( config. hash_rewire_activation_height, None ) ;
340386 }
341387
342388 #[ test]
@@ -348,7 +394,6 @@ mod tests {
348394 assert_eq ! ( config. base_fee_redirect_activation_height, None ) ;
349395 assert_eq ! ( config. mint_precompile_activation_height, None ) ;
350396 assert_eq ! ( config. contract_size_limit, None ) ;
351- assert_eq ! ( config. hash_rewire_activation_height, None ) ;
352397 }
353398
354399 #[ test]
@@ -364,7 +409,7 @@ mod tests {
364409 mint_precompile_activation_height : Some ( 0 ) ,
365410 contract_size_limit : None ,
366411 contract_size_limit_activation_height : None ,
367- hash_rewire_activation_height : None ,
412+ canonical_hash_activation_height : None ,
368413 } ;
369414 assert ! ( config_with_sink. validate( ) . is_ok( ) ) ;
370415 }
@@ -379,7 +424,7 @@ mod tests {
379424 mint_precompile_activation_height : None ,
380425 contract_size_limit : None ,
381426 contract_size_limit_activation_height : None ,
382- hash_rewire_activation_height : None ,
427+ canonical_hash_activation_height : None ,
383428 } ;
384429
385430 assert_eq ! ( config. base_fee_sink_for_block( 4 ) , None ) ;
@@ -407,13 +452,11 @@ mod tests {
407452 config. mint_admin,
408453 Some ( address!( "00000000000000000000000000000000000000aa" ) )
409454 ) ;
410- assert_eq ! ( config. hash_rewire_activation_height, None ) ;
411455
412456 let json_without_sink = json ! ( { } ) ;
413457 let config: ChainspecEvolveConfig = serde_json:: from_value ( json_without_sink) . unwrap ( ) ;
414458 assert_eq ! ( config. base_fee_sink, None ) ;
415459 assert_eq ! ( config. mint_admin, None ) ;
416- assert_eq ! ( config. hash_rewire_activation_height, None ) ;
417460 }
418461
419462 #[ test]
@@ -513,16 +556,28 @@ mod tests {
513556 }
514557
515558 #[ test]
516- fn test_hash_rewire_activation_height_parsed ( ) {
559+ fn test_canonical_hash_activation_from_chainspec ( ) {
517560 let extras = json ! ( {
518- "hashRewireActivationHeight " : 500
561+ "canonicalHashActivationHeight " : 500
519562 } ) ;
520563
521564 let chainspec = create_test_chainspec_with_extras ( Some ( extras) ) ;
522565 let config = EvolvePayloadBuilderConfig :: from_chain_spec ( & chainspec) . unwrap ( ) ;
523566
524- assert_eq ! ( config. hash_rewire_activation_height, Some ( 500 ) ) ;
525- assert ! ( config. is_hash_rewire_active_for_block( 600 ) ) ;
526- assert ! ( !config. is_hash_rewire_active_for_block( 400 ) ) ;
567+ assert_eq ! ( config. canonical_hash_activation_height, Some ( 500 ) ) ;
568+ // Before activation: legacy mode (not enforced)
569+ assert ! ( !config. is_canonical_hash_enforced( 499 ) ) ;
570+ // At and after activation: canonical mode (enforced)
571+ assert ! ( config. is_canonical_hash_enforced( 500 ) ) ;
572+ assert ! ( config. is_canonical_hash_enforced( 600 ) ) ;
573+ }
574+
575+ #[ test]
576+ fn test_canonical_hash_default_enforces_from_genesis ( ) {
577+ // When not configured, canonical hashes should be enforced from genesis
578+ let config = EvolvePayloadBuilderConfig :: new ( ) ;
579+ assert_eq ! ( config. canonical_hash_activation_height, None ) ;
580+ assert ! ( config. is_canonical_hash_enforced( 0 ) ) ;
581+ assert ! ( config. is_canonical_hash_enforced( 1000 ) ) ;
527582 }
528583}
0 commit comments