11use ethlambda_types:: { block:: SignedBlockWithAttestation , primitives:: H256 , state:: Checkpoint } ;
2+ use ssz:: Decode as SszDecode ;
23use ssz_derive:: { Decode , Encode } ;
34use ssz_types:: typenum;
45
@@ -102,7 +103,7 @@ pub struct Status {
102103type MaxRequestBlocks = typenum:: U1024 ;
103104type MaxErrorMessageLength = typenum:: U256 ;
104105
105- pub type BlocksByRootRequest = ssz_types:: VariableList < H256 , MaxRequestBlocks > ;
106+ pub type RequestedBlockRoots = ssz_types:: VariableList < H256 , MaxRequestBlocks > ;
106107
107108/// Error message type for non-success responses.
108109/// SSZ-encoded as List[byte, 256] per spec.
@@ -121,3 +122,60 @@ pub fn error_message(msg: impl AsRef<str>) -> ErrorMessage {
121122 let vec = truncated. to_vec ( ) ;
122123 ErrorMessage :: new ( vec) . expect ( "error message fits in 256 bytes" )
123124}
125+
126+ #[ derive( Debug , Clone , Encode , Decode ) ]
127+ pub struct BlocksByRootRequest {
128+ pub roots : RequestedBlockRoots ,
129+ }
130+
131+ impl BlocksByRootRequest {
132+ /// Decode from SSZ bytes with backward compatibility.
133+ ///
134+ /// Tries to decode as new format (container with `roots` field) first.
135+ /// Falls back to old format (transparent - direct list of roots) if that fails.
136+ pub fn from_ssz_bytes_compat ( bytes : & [ u8 ] ) -> Result < Self , ssz:: DecodeError > {
137+ // Try new format (container) first
138+ SszDecode :: from_ssz_bytes ( bytes) . or_else ( |_| {
139+ // Fall back to old format (transparent/direct list)
140+ SszDecode :: from_ssz_bytes ( bytes) . map ( |roots| Self { roots } )
141+ } )
142+ }
143+ }
144+
145+ #[ cfg( test) ]
146+ mod tests {
147+ use super :: * ;
148+ use ssz:: Encode as SszEncode ;
149+
150+ #[ test]
151+ fn test_blocks_by_root_backward_compatibility ( ) {
152+ // Create some test roots
153+ let root1 = H256 :: from_slice ( & [ 1u8 ; 32 ] ) ;
154+ let root2 = H256 :: from_slice ( & [ 2u8 ; 32 ] ) ;
155+ let roots_list =
156+ RequestedBlockRoots :: new ( vec ! [ root1, root2] ) . expect ( "Failed to create roots list" ) ;
157+
158+ // Encode as old format (direct list, similar to transparent)
159+ let old_format_bytes = roots_list. as_ssz_bytes ( ) ;
160+
161+ // Encode as new format (container)
162+ let new_request = BlocksByRootRequest {
163+ roots : roots_list. clone ( ) ,
164+ } ;
165+ let new_format_bytes = new_request. as_ssz_bytes ( ) ;
166+
167+ // Both formats should decode successfully
168+ let decoded_from_old = BlocksByRootRequest :: from_ssz_bytes_compat ( & old_format_bytes)
169+ . expect ( "Failed to decode old format" ) ;
170+ let decoded_from_new = BlocksByRootRequest :: from_ssz_bytes_compat ( & new_format_bytes)
171+ . expect ( "Failed to decode new format" ) ;
172+
173+ // Both should have the same roots
174+ assert_eq ! ( decoded_from_old. roots. len( ) , 2 ) ;
175+ assert_eq ! ( decoded_from_new. roots. len( ) , 2 ) ;
176+ assert_eq ! ( decoded_from_old. roots[ 0 ] , root1) ;
177+ assert_eq ! ( decoded_from_old. roots[ 1 ] , root2) ;
178+ assert_eq ! ( decoded_from_new. roots[ 0 ] , root1) ;
179+ assert_eq ! ( decoded_from_new. roots[ 1 ] , root2) ;
180+ }
181+ }
0 commit comments