Skip to content

Commit 89cbc53

Browse files
committed
P2p: call preliminary_headers_check earlier
1 parent 632f4af commit 89cbc53

File tree

3 files changed

+200
-19
lines changed

3 files changed

+200
-19
lines changed

p2p/src/sync/peer/block_manager.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,15 @@ where
716716
self.incoming.peers_best_block_that_we_have.as_displayable()
717717
);
718718

719+
// Now use preliminary_headers_check; this can be done because the first header
720+
// is known to be connected to the chainstate.
721+
{
722+
let new_block_headers = new_block_headers.clone();
723+
self.chainstate_handle
724+
.call(move |c| Ok(c.preliminary_headers_check(&new_block_headers)?))
725+
.await?;
726+
}
727+
719728
if !self.incoming.requested_blocks.is_empty() {
720729
// We are already downloading blocks, so bail out.
721730
// Note that we unconditionally replace pending_headers with new_block_headers
@@ -732,15 +741,6 @@ where
732741
return Ok(());
733742
}
734743

735-
// Now use preliminary_headers_check; this can be done because the first header
736-
// is known to be connected to the chainstate.
737-
{
738-
let new_block_headers = new_block_headers.clone();
739-
self.chainstate_handle
740-
.call(move |c| Ok(c.preliminary_headers_check(&new_block_headers)?))
741-
.await?;
742-
}
743-
744744
self.request_blocks(new_block_headers)
745745
}
746746

p2p/src/sync/tests/header_list_response.rs

Lines changed: 176 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,27 @@
1515

1616
use std::{iter, sync::Arc, time::Duration};
1717

18+
use itertools::Itertools as _;
19+
1820
use chainstate::ban_score::BanScore;
1921
use 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;
2127
use 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

2435
use 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)]
3849
async 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)]
8495
async 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)]
129140
async 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)]
172183
async 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+
}

p2p/src/sync/tests/helpers/mod.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ impl TestNode {
251251

252252
/// Panics if the sync manager returns an error.
253253
pub async fn assert_no_error(&mut self) {
254+
log::debug!("Asserting no error");
254255
expect_no_recv!(self.error_receiver);
255256
}
256257

@@ -362,6 +363,8 @@ impl TestNode {
362363
/// like NewTipReceived/NewChainstateTip etc).
363364
// TODO: Rename the function
364365
pub async fn assert_no_peer_manager_event(&mut self) {
366+
log::debug!("Asserting no peer mgr event");
367+
365368
time::timeout(SHORT_TIMEOUT, async {
366369
loop {
367370
let peer_event = self.peer_manager_event_receiver.recv().await.unwrap();
@@ -400,10 +403,16 @@ impl TestNode {
400403

401404
/// Panics if the sync manager sends a message.
402405
pub async fn assert_no_sync_message(&mut self) {
406+
log::debug!("Asserting no sync message");
407+
403408
let future = async {
404409
tokio::select! {
405-
_ = self.block_sync_msg_receiver.recv() => {},
406-
_ = self.transaction_sync_msg_receiver.recv() => {},
410+
msg = self.block_sync_msg_receiver.recv() => {
411+
log::debug!("Got sync msg {msg:?} while expecting none");
412+
},
413+
msg = self.transaction_sync_msg_receiver.recv() => {
414+
log::debug!("Got sync msg {msg:?} while expecting none");
415+
},
407416
}
408417
};
409418

@@ -415,6 +424,8 @@ impl TestNode {
415424
expected_peer: PeerId,
416425
expected_score: u32,
417426
) {
427+
log::debug!("Asserting peer score adjustment");
428+
418429
let (adjusted_peer, score) = self.receive_adjust_peer_score_event().await;
419430
assert_eq!(adjusted_peer, expected_peer);
420431
assert_eq!(score, expected_score);
@@ -429,6 +440,8 @@ impl TestNode {
429440
}
430441

431442
pub async fn join_subsystem_manager(self) {
443+
log::debug!("Joining subsystem mgr");
444+
432445
// Shutdown sync manager first
433446
drop(self.syncing_event_sender);
434447
let _ = self.sync_manager_handle.await;

0 commit comments

Comments
 (0)