Skip to content

Commit 790abdc

Browse files
committed
P2p: add test for peer extending an invalidated branch
1 parent 89cbc53 commit 790abdc

File tree

1 file changed

+149
-0
lines changed

1 file changed

+149
-0
lines changed

p2p/src/sync/tests/header_list_response.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)