1515
1616use std:: { iter, sync:: Arc , time:: Duration } ;
1717
18+ use itertools:: Itertools as _;
19+
1820use chainstate:: ban_score:: BanScore ;
1921use chainstate_test_framework:: TestFramework ;
20- use common:: { chain:: config:: create_unit_test_config, primitives:: Idable } ;
22+ use common:: {
23+ chain:: { config:: create_unit_test_config, GenBlock } ,
24+ primitives:: { BlockHeight , Id , Idable } ,
25+ } ;
26+ use logging:: log;
2127use p2p_test_utils:: create_n_blocks;
22- use test_utils:: random:: Seed ;
28+ use randomness:: Rng as _;
29+ use test_utils:: {
30+ assert_matches,
31+ random:: { make_seedable_rng, Seed } ,
32+ BasicTestTimeGetter ,
33+ } ;
2334
2435use crate :: {
2536 error:: ProtocolError ,
26- message:: { BlockListRequest , BlockSyncMessage , HeaderList } ,
27- sync:: tests:: helpers:: TestNode ,
37+ message:: { BlockListRequest , BlockResponse , BlockSyncMessage , HeaderList } ,
38+ sync:: { test_helpers :: make_new_blocks , tests:: helpers:: TestNode } ,
2839 test_helpers:: { for_each_protocol_version, test_p2p_config} ,
2940 types:: peer_id:: PeerId ,
3041 P2pConfig , P2pError ,
@@ -37,7 +48,7 @@ use crate::{
3748#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
3849async fn header_count_limit_exceeded ( #[ case] seed : Seed ) {
3950 for_each_protocol_version ( |protocol_version| async move {
40- let mut rng = test_utils :: random :: make_seedable_rng ( seed) ;
51+ let mut rng = make_seedable_rng ( seed) ;
4152
4253 let chain_config = Arc :: new ( create_unit_test_config ( ) ) ;
4354 let mut tf = TestFramework :: builder ( & mut rng)
@@ -83,7 +94,7 @@ async fn header_count_limit_exceeded(#[case] seed: Seed) {
8394#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
8495async fn unordered_headers ( #[ case] seed : Seed ) {
8596 for_each_protocol_version ( |protocol_version| async move {
86- let mut rng = test_utils :: random :: make_seedable_rng ( seed) ;
97+ let mut rng = make_seedable_rng ( seed) ;
8798
8899 let chain_config = Arc :: new ( create_unit_test_config ( ) ) ;
89100 let mut tf = TestFramework :: builder ( & mut rng)
@@ -128,7 +139,7 @@ async fn unordered_headers(#[case] seed: Seed) {
128139#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
129140async fn disconnected_headers ( #[ case] seed : Seed ) {
130141 for_each_protocol_version ( |protocol_version| async move {
131- let mut rng = test_utils :: random :: make_seedable_rng ( seed) ;
142+ let mut rng = make_seedable_rng ( seed) ;
132143
133144 let chain_config = Arc :: new ( create_unit_test_config ( ) ) ;
134145 let mut tf = TestFramework :: builder ( & mut rng)
@@ -171,7 +182,7 @@ async fn disconnected_headers(#[case] seed: Seed) {
171182#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
172183async fn valid_headers ( #[ case] seed : Seed ) {
173184 for_each_protocol_version ( |protocol_version| async move {
174- let mut rng = test_utils :: random :: make_seedable_rng ( seed) ;
185+ let mut rng = make_seedable_rng ( seed) ;
175186
176187 let chain_config = Arc :: new ( create_unit_test_config ( ) ) ;
177188 let mut tf = TestFramework :: builder ( & mut rng)
@@ -246,3 +257,160 @@ async fn disconnect() {
246257 } )
247258 . await ;
248259}
260+
261+ // When handling a header list, the node must check headers' validity first, and only then check
262+ // if some blocks have already been requested.
263+ // The actual test happens with "make_branch2_invalid=true":
264+ // 1) Make a forked chain with 2 branches; the 1st one is good, the 2nd one has an invalid header.
265+ // 2) The peer sends a header list for the 1st branch; the node responds with a block list request.
266+ // 3) The peer sends a header list for the 2nd branch.
267+ // Expected result: the node sees that the new header list has an invalid header and adjusts
268+ // the peer score immediately.
269+ // The case "make_branch2_invalid=false" exists for completeness; all headers on the 2nd branch
270+ // are ok in this case and the node is expected to send another BlockListRequest and accept the
271+ // corresponding blocks without punishing the peer.
272+ #[ tracing:: instrument( skip( seed) ) ]
273+ #[ rstest:: rstest]
274+ #[ trace]
275+ #[ case( Seed :: from_entropy( ) ) ]
276+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
277+ async fn header_check_happens_before_checking_if_blocks_were_requested (
278+ #[ case] seed : Seed ,
279+ #[ values( false , true ) ] make_branch2_invalid : bool ,
280+ ) {
281+ for_each_protocol_version ( |protocol_version| async move {
282+ let mut rng = make_seedable_rng ( seed) ;
283+ let chain_config = Arc :: new ( common:: chain:: config:: create_unit_test_config ( ) ) ;
284+ let time_getter = BasicTestTimeGetter :: new ( ) ;
285+
286+ let node_blocks_count = rng. gen_range ( 1 ..3 ) ;
287+ let branch1_len = rng. gen_range ( 3 ..5 ) ;
288+ let branch2_len = rng. gen_range ( 3 ..5 ) ;
289+
290+ log:: debug!(
291+ "node_blocks_count = {}, branch1_len = {}, branch2_len = {}" ,
292+ node_blocks_count,
293+ branch1_len,
294+ branch2_len
295+ ) ;
296+
297+ let node_blocks = make_new_blocks (
298+ & chain_config,
299+ None ,
300+ & time_getter. get_time_getter ( ) ,
301+ node_blocks_count,
302+ & mut rng,
303+ ) ;
304+
305+ let branch1_blocks = make_new_blocks (
306+ & chain_config,
307+ node_blocks. last ( ) ,
308+ & time_getter. get_time_getter ( ) ,
309+ branch1_len,
310+ & mut rng,
311+ ) ;
312+
313+ let branch2_blocks = make_new_blocks (
314+ & chain_config,
315+ node_blocks. last ( ) ,
316+ & time_getter. get_time_getter ( ) ,
317+ branch2_len,
318+ & mut rng,
319+ ) ;
320+
321+ let chain_config = if make_branch2_invalid {
322+ let branch2_invalid_header_idx =
323+ rng. gen_range ( 0 ..std:: cmp:: min ( branch1_blocks. len ( ) , branch2_blocks. len ( ) ) ) ;
324+
325+ // To make one of the headers on branch2 invalid, specify a checkpoint referring to
326+ // a block on branch1.
327+ let checkpoint_height =
328+ BlockHeight :: new ( ( branch2_invalid_header_idx + node_blocks. len ( ) + 1 ) as u64 ) ;
329+ let checkpoint_block_id: Id < GenBlock > =
330+ branch1_blocks[ branch2_invalid_header_idx] . get_id ( ) . into ( ) ;
331+
332+ let chain_config = Arc :: new (
333+ common:: chain:: config:: create_unit_test_config_builder ( )
334+ . checkpoints ( [ ( checkpoint_height, checkpoint_block_id) ] . into ( ) )
335+ . build ( ) ,
336+ ) ;
337+
338+ chain_config
339+ } else {
340+ chain_config
341+ } ;
342+
343+ let p2p_config = Arc :: new ( test_p2p_config ( ) ) ;
344+
345+ let mut node = TestNode :: builder ( protocol_version)
346+ . with_chain_config ( chain_config)
347+ . with_p2p_config ( Arc :: clone ( & p2p_config) )
348+ . with_time_getter ( time_getter. get_time_getter ( ) )
349+ . with_blocks ( node_blocks. clone ( ) )
350+ . build ( )
351+ . await ;
352+
353+ let peer = node. connect_peer ( PeerId :: new ( ) , protocol_version) . await ;
354+
355+ log:: debug!( "Peer sends HeaderList for branch1" ) ;
356+ peer. send_headers ( branch1_blocks. iter ( ) . map ( |block| block. header ( ) . clone ( ) ) . collect_vec ( ) )
357+ . await ;
358+
359+ log:: debug!( "Expecting BlockListRequest for branch 1" ) ;
360+ assert_eq ! (
361+ node. get_sent_block_sync_message( ) . await . 1 ,
362+ BlockSyncMessage :: BlockListRequest ( BlockListRequest :: new(
363+ branch1_blocks. iter( ) . map( |block| block. get_id( ) ) . collect_vec( )
364+ ) ) ,
365+ ) ;
366+
367+ log:: debug!( "Peer sends HeaderList for branch2" ) ;
368+ peer. send_headers ( branch2_blocks. iter ( ) . map ( |block| block. header ( ) . clone ( ) ) . collect_vec ( ) )
369+ . await ;
370+
371+ if make_branch2_invalid {
372+ // The node should check the headers right away and discover that one of them is invalid.
373+ node. assert_peer_score_adjustment ( peer. get_id ( ) , 100 ) . await ;
374+ node. assert_no_sync_message ( ) . await ;
375+ } else {
376+ // If the headers are ok, the node should accept the previously requested blocks
377+ // and make a new block request for the new ones.
378+
379+ for block in & branch1_blocks {
380+ log:: debug!( "Peer sends BlockResponse for branch 1" ) ;
381+ peer. send_block_sync_message ( BlockSyncMessage :: BlockResponse ( BlockResponse :: new (
382+ block. clone ( ) ,
383+ ) ) )
384+ . await ;
385+ }
386+
387+ log:: debug!( "Expecting BlockListRequest for branch 2" ) ;
388+ assert_eq ! (
389+ node. get_sent_block_sync_message( ) . await . 1 ,
390+ BlockSyncMessage :: BlockListRequest ( BlockListRequest :: new(
391+ branch2_blocks. iter( ) . map( |block| block. get_id( ) ) . collect_vec( )
392+ ) ) ,
393+ ) ;
394+
395+ for block in & branch2_blocks {
396+ log:: debug!( "Peer sends BlockResponse for branch 2" ) ;
397+ peer. send_block_sync_message ( BlockSyncMessage :: BlockResponse ( BlockResponse :: new (
398+ block. clone ( ) ,
399+ ) ) )
400+ . await ;
401+ }
402+
403+ log:: debug!( "Expecting final HeaderListRequest" ) ;
404+ assert_matches ! (
405+ node. get_sent_block_sync_message( ) . await . 1 ,
406+ BlockSyncMessage :: HeaderListRequest ( _)
407+ ) ;
408+ }
409+
410+ node. assert_no_error ( ) . await ;
411+ node. assert_no_peer_manager_event ( ) . await ;
412+ node. assert_no_sync_message ( ) . await ;
413+ node. join_subsystem_manager ( ) . await ;
414+ } )
415+ . await ;
416+ }
0 commit comments