1- use std:: collections:: HashSet ;
1+ use std:: collections:: { HashMap , HashSet } ;
22
33use ethlambda_storage:: Store ;
44use libp2p:: { PeerId , request_response} ;
@@ -12,7 +12,9 @@ use ethlambda_types::primitives::HashTreeRoot as _;
1212use ethlambda_types:: { block:: SignedBlock , primitives:: H256 } ;
1313
1414use super :: {
15- BLOCKS_BY_ROOT_PROTOCOL_V1 , BlocksByRootRequest , Request , Response , ResponsePayload , Status ,
15+ BLOCKS_BY_ROOT_PROTOCOL_V1 , BlocksByRangeRequest , BlocksByRootRequest , MAX_REQUEST_BLOCKS ,
16+ Request , Response , ResponsePayload , Status ,
17+ messages:: { ResponseCode , error_message} ,
1618} ;
1719use crate :: {
1820 BACKOFF_MULTIPLIER , INITIAL_BACKOFF_MS , MAX_FETCH_RETRIES , P2PServer , PendingRequest ,
@@ -42,6 +44,13 @@ pub async fn handle_req_resp_message(
4244 ) ;
4345 handle_blocks_by_root_request ( server, request, channel, peer) . await ;
4446 }
47+ Request :: BlocksByRange ( request) => {
48+ info ! (
49+ kind = "blocks_by_range_request" ,
50+ peer_count, "P2P message received"
51+ ) ;
52+ handle_blocks_by_range_request ( server, request, channel, peer) . await ;
53+ }
4554 }
4655 }
4756 request_response:: Message :: Response {
@@ -55,11 +64,8 @@ pub async fn handle_req_resp_message(
5564 info ! ( kind = "status_response" , peer_count, "P2P message received" ) ;
5665 handle_status_response ( status, peer) . await ;
5766 }
58- ResponsePayload :: BlocksByRoot ( blocks) => {
59- info ! (
60- kind = "blocks_by_root_response" ,
61- peer_count, "P2P message received"
62- ) ;
67+ ResponsePayload :: Blocks ( blocks) => {
68+ info ! ( kind = "blocks_response" , peer_count, "P2P message received" ) ;
6369 handle_blocks_by_root_response ( server, blocks, peer, request_id, ctx)
6470 . await ;
6571 }
@@ -136,10 +142,99 @@ async fn handle_blocks_by_root_request(
136142 let found = blocks. len ( ) ;
137143 info ! ( %peer, num_roots, found, "Responding to BlocksByRoot request" ) ;
138144
139- let response = Response :: success ( ResponsePayload :: BlocksByRoot ( blocks) ) ;
145+ let response = Response :: success ( ResponsePayload :: Blocks ( blocks) ) ;
146+ server. swarm_handle . send_response ( channel, response) ;
147+ }
148+
149+ async fn handle_blocks_by_range_request (
150+ server : & mut P2PServer ,
151+ request : BlocksByRangeRequest ,
152+ channel : request_response:: ResponseChannel < Response > ,
153+ peer : PeerId ,
154+ ) {
155+ info ! (
156+ %peer,
157+ start_slot = request. start_slot,
158+ count = request. count,
159+ step = request. step,
160+ "Received BlocksByRange request"
161+ ) ;
162+
163+ if request. step == 0 || request. count == 0 || request. count > MAX_REQUEST_BLOCKS {
164+ let response = Response :: error (
165+ ResponseCode :: INVALID_REQUEST ,
166+ error_message ( "invalid BlocksByRange request" ) ,
167+ ) ;
168+ server. swarm_handle . send_response ( channel, response) ;
169+ return ;
170+ }
171+
172+ let blocks = canonical_blocks_by_range (
173+ & server. store ,
174+ request. start_slot ,
175+ request. count ,
176+ request. step ,
177+ ) ;
178+
179+ info ! (
180+ %peer,
181+ start_slot = request. start_slot,
182+ count = request. count,
183+ step = request. step,
184+ found = blocks. len( ) ,
185+ "Responding to BlocksByRange request"
186+ ) ;
187+
188+ let response = Response :: success ( ResponsePayload :: Blocks ( blocks) ) ;
140189 server. swarm_handle . send_response ( channel, response) ;
141190}
142191
192+ fn canonical_blocks_by_range (
193+ store : & Store ,
194+ start_slot : u64 ,
195+ count : u64 ,
196+ step : u64 ,
197+ ) -> Vec < SignedBlock > {
198+ if count == 0 {
199+ return Vec :: new ( ) ;
200+ }
201+
202+ let Some ( end_slot) = count
203+ . checked_sub ( 1 )
204+ . and_then ( |value| value. checked_mul ( step) )
205+ . and_then ( |last_offset| start_slot. checked_add ( last_offset) )
206+ else {
207+ return Vec :: new ( ) ;
208+ } ;
209+
210+ let mut roots_by_slot = HashMap :: new ( ) ;
211+ let mut current_root = store. head ( ) ;
212+
213+ while !current_root. is_zero ( ) {
214+ let Some ( header) = store. get_block_header ( & current_root) else {
215+ break ;
216+ } ;
217+
218+ if header. slot < start_slot {
219+ break ;
220+ }
221+
222+ if header. slot <= end_slot && ( header. slot - start_slot) . is_multiple_of ( step) {
223+ roots_by_slot. insert ( header. slot , current_root) ;
224+ }
225+
226+ current_root = header. parent_root ;
227+ }
228+
229+ ( 0 ..count)
230+ . filter_map ( |index| {
231+ let slot = start_slot. checked_add ( index. checked_mul ( step) ?) ?;
232+ let root = roots_by_slot. get ( & slot) ?;
233+ store. get_signed_block ( root)
234+ } )
235+ . collect ( )
236+ }
237+
143238async fn handle_blocks_by_root_response (
144239 server : & mut P2PServer ,
145240 blocks : Vec < SignedBlock > ,
@@ -313,3 +408,67 @@ async fn handle_fetch_failure(
313408
314409 send_after ( backoff, ctx. clone ( ) , p2p_protocol:: RetryBlockFetch { root } ) ;
315410}
411+
412+ #[ cfg( test) ]
413+ mod tests {
414+ use super :: * ;
415+ use ethlambda_storage:: { ForkCheckpoints , backend:: InMemoryBackend } ;
416+ use ethlambda_types:: {
417+ attestation:: XmssSignature ,
418+ block:: { Block , BlockBody , BlockSignatures } ,
419+ signature:: SIGNATURE_SIZE ,
420+ state:: State ,
421+ } ;
422+ use libssz_types:: SszList ;
423+ use std:: sync:: Arc ;
424+
425+ fn signed_block ( slot : u64 , parent_root : H256 ) -> SignedBlock {
426+ SignedBlock {
427+ message : Block {
428+ slot,
429+ proposer_index : 0 ,
430+ parent_root,
431+ state_root : H256 :: ZERO ,
432+ body : BlockBody :: default ( ) ,
433+ } ,
434+ signature : BlockSignatures {
435+ attestation_signatures : SszList :: new ( ) ,
436+ proposer_signature : XmssSignature :: try_from ( vec ! [ 0u8 ; SIGNATURE_SIZE ] ) . unwrap ( ) ,
437+ } ,
438+ }
439+ }
440+
441+ #[ test]
442+ fn blocks_by_range_returns_canonical_blocks_in_requested_order ( ) {
443+ let backend = Arc :: new ( InMemoryBackend :: new ( ) ) ;
444+ let mut store = Store :: from_anchor_state ( backend, State :: from_genesis ( 0 , vec ! [ ] ) ) ;
445+
446+ let block_1 = signed_block ( 1 , store. head ( ) ) ;
447+ let root_1 = block_1. message . hash_tree_root ( ) ;
448+ store. insert_signed_block ( root_1, block_1) ;
449+
450+ let block_2 = signed_block ( 2 , root_1) ;
451+ let root_2 = block_2. message . hash_tree_root ( ) ;
452+ store. insert_signed_block ( root_2, block_2) ;
453+
454+ let side_block_3 = signed_block ( 3 , root_1) ;
455+ let side_root_3 = side_block_3. message . hash_tree_root ( ) ;
456+ store. insert_signed_block ( side_root_3, side_block_3) ;
457+
458+ let block_4 = signed_block ( 4 , root_2) ;
459+ let root_4 = block_4. message . hash_tree_root ( ) ;
460+ store. insert_signed_block ( root_4, block_4) ;
461+ store. update_checkpoints ( ForkCheckpoints :: head_only ( root_4) ) ;
462+
463+ let blocks = canonical_blocks_by_range ( & store, 1 , 4 , 1 ) ;
464+ let slots: Vec < _ > = blocks. iter ( ) . map ( |block| block. message . slot ) . collect ( ) ;
465+ let roots: Vec < _ > = blocks
466+ . iter ( )
467+ . map ( |block| block. message . hash_tree_root ( ) )
468+ . collect ( ) ;
469+
470+ assert_eq ! ( slots, vec![ 1 , 2 , 4 ] ) ;
471+ assert_eq ! ( roots, vec![ root_1, root_2, root_4] ) ;
472+ assert ! ( !roots. contains( & side_root_3) ) ;
473+ }
474+ }
0 commit comments