@@ -414,3 +414,152 @@ async fn header_check_happens_before_checking_if_blocks_were_requested(
414414 } )
415415 . await ;
416416}
417+
418+ // Check that if the peer tries to extend a branch that has been invalidated in the node,
419+ // the node will detect it when validating headers and won't try to download blocks that are
420+ // known to be invalid.
421+ // The actual test happens with "invalidate_branch2=true":
422+ // 1) The node has a forked chain, where the 2nd branch has a manually invalidated block.
423+ // 2) The peer sends a HeaderList that may include some part of the invalidated branch and also adds
424+ // new blocks on top of it.
425+ // Expected result: the node adjusts peer's score immediately, without trying to download the
426+ // blocks first.
427+ // The case "invalidate_branch2=false" exists for completeness; no blocks on the 2nd branch are
428+ // invalidated in this case and the node is expected to send a BlockListRequest and accept the
429+ // corresponding blocks without punishing the peer.
430+ #[ tracing:: instrument( skip( seed) ) ]
431+ #[ rstest:: rstest]
432+ #[ trace]
433+ #[ case( Seed :: from_entropy( ) ) ]
434+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
435+ async fn extending_invalidated_chain_should_fail_at_header_check (
436+ #[ case] seed : Seed ,
437+ #[ values( false , true ) ] invalidate_branch2 : bool ,
438+ ) {
439+ for_each_protocol_version ( |protocol_version| async move {
440+ let mut rng = make_seedable_rng ( seed) ;
441+ let chain_config = Arc :: new ( common:: chain:: config:: create_unit_test_config ( ) ) ;
442+ let time_getter = BasicTestTimeGetter :: new ( ) ;
443+
444+ let common_blocks_count = rng. gen_range ( 1 ..3 ) ;
445+ let branch1_blocks_count = rng. gen_range ( 3 ..5 ) ;
446+ let branch2_known_blocks_count = rng. gen_range ( 1 ..3 ) ;
447+ let branch2_unknown_blocks_count = rng. gen_range ( 1 ..3 ) ;
448+
449+ log:: debug!(
450+ "common blk = {}, branch1 blk = {}, branch2 known blk = {}, branch2 unknown blk = {}" ,
451+ common_blocks_count,
452+ branch1_blocks_count,
453+ branch2_known_blocks_count,
454+ branch2_unknown_blocks_count
455+ ) ;
456+
457+ let common_blocks = make_new_blocks (
458+ & chain_config,
459+ None ,
460+ & time_getter. get_time_getter ( ) ,
461+ common_blocks_count,
462+ & mut rng,
463+ ) ;
464+
465+ let branch1_blocks = make_new_blocks (
466+ & chain_config,
467+ common_blocks. last ( ) ,
468+ & time_getter. get_time_getter ( ) ,
469+ branch1_blocks_count,
470+ & mut rng,
471+ ) ;
472+
473+ let branch2_known_blocks = make_new_blocks (
474+ & chain_config,
475+ common_blocks. last ( ) ,
476+ & time_getter. get_time_getter ( ) ,
477+ branch2_known_blocks_count,
478+ & mut rng,
479+ ) ;
480+
481+ let branch2_unknown_blocks = make_new_blocks (
482+ & chain_config,
483+ branch2_known_blocks. last ( ) ,
484+ & time_getter. get_time_getter ( ) ,
485+ branch2_unknown_blocks_count,
486+ & mut rng,
487+ ) ;
488+
489+ let p2p_config = Arc :: new ( test_p2p_config ( ) ) ;
490+
491+ let mut node = TestNode :: builder ( protocol_version)
492+ . with_chain_config ( chain_config)
493+ . with_p2p_config ( Arc :: clone ( & p2p_config) )
494+ . with_time_getter ( time_getter. get_time_getter ( ) )
495+ . with_blocks (
496+ [
497+ common_blocks. as_slice ( ) ,
498+ branch1_blocks. as_slice ( ) ,
499+ branch2_known_blocks. as_slice ( ) ,
500+ ]
501+ . concat ( ) ,
502+ )
503+ . build ( )
504+ . await ;
505+
506+ if invalidate_branch2 {
507+ let block_id_to_invalidate =
508+ branch2_known_blocks[ rng. gen_range ( 0 ..branch2_known_blocks. len ( ) ) ] . get_id ( ) ;
509+ node. chainstate ( )
510+ . call_mut ( move |cs| cs. invalidate_block ( & block_id_to_invalidate) )
511+ . await
512+ . unwrap ( )
513+ . unwrap ( )
514+ }
515+
516+ let peer = node. connect_peer ( PeerId :: new ( ) , protocol_version) . await ;
517+
518+ let branch2_first_known_block_to_send_idx = rng. gen_range ( 0 ..=branch2_known_blocks. len ( ) ) ;
519+ let branch2_headers = branch2_known_blocks[ branch2_first_known_block_to_send_idx..]
520+ . iter ( )
521+ . chain ( branch2_unknown_blocks. iter ( ) )
522+ . map ( |block| block. header ( ) . clone ( ) )
523+ . collect_vec ( ) ;
524+
525+ log:: debug!( "Peer sends HeaderList for branch2" ) ;
526+ peer. send_headers ( branch2_headers) . await ;
527+
528+ if invalidate_branch2 {
529+ // The node should discover that the headers extend an invalidated chain, without
530+ // the need to download the actual blocks, and discourage the peer right away.
531+ node. assert_peer_score_adjustment ( peer. get_id ( ) , 100 ) . await ;
532+ node. assert_no_sync_message ( ) . await ;
533+ } else {
534+ // If the headers are ok, the node should make a block request for the unknown blocks.
535+
536+ log:: debug!( "Expecting BlockListRequest for branch2_unknown_blocks" ) ;
537+ assert_eq ! (
538+ node. get_sent_block_sync_message( ) . await . 1 ,
539+ BlockSyncMessage :: BlockListRequest ( BlockListRequest :: new(
540+ branch2_unknown_blocks. iter( ) . map( |block| block. get_id( ) ) . collect_vec( )
541+ ) ) ,
542+ ) ;
543+
544+ for block in & branch2_unknown_blocks {
545+ log:: debug!( "Peer sends BlockResponse for branch2_unknown_blocks" ) ;
546+ peer. send_block_sync_message ( BlockSyncMessage :: BlockResponse ( BlockResponse :: new (
547+ block. clone ( ) ,
548+ ) ) )
549+ . await ;
550+ }
551+
552+ log:: debug!( "Expecting final HeaderListRequest" ) ;
553+ assert_matches ! (
554+ node. get_sent_block_sync_message( ) . await . 1 ,
555+ BlockSyncMessage :: HeaderListRequest ( _)
556+ ) ;
557+ }
558+
559+ node. assert_no_error ( ) . await ;
560+ node. assert_no_peer_manager_event ( ) . await ;
561+ node. assert_no_sync_message ( ) . await ;
562+ node. join_subsystem_manager ( ) . await ;
563+ } )
564+ . await ;
565+ }
0 commit comments