From 986fa5591f146ced040a4f16336cba2a432b5164 Mon Sep 17 00:00:00 2001 From: Yiannis Marangos Date: Thu, 25 Jul 2024 13:13:40 +0300 Subject: [PATCH] fix(node)!: Relax initialization if HEAD is the same as the stored one (#347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: MikoĊ‚aj Florkiewicz --- node/src/syncer.rs | 64 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/node/src/syncer.rs b/node/src/syncer.rs index 7c088760..2262235e 100644 --- a/node/src/syncer.rs +++ b/node/src/syncer.rs @@ -611,9 +611,23 @@ where let network_head = p2p.get_head_header().await?; - // Insert HEAD to the store and initialize header-sub. - // Normal insertion checks still apply here. - store.insert(network_head.clone()).await?; + // If the network head and the store head have the same height, + // then `insert` will error because of insertion contraints. + // However, if both headers are the exactly the same, we + // can skip inserting, as the header is already there. + // + // This can happen in case of fast node restart. + let try_insert = match store.get_head().await { + Ok(store_head) => store_head != network_head, + Err(StoreError::NotFound) => true, + Err(e) => return Err(e.into()), + }; + + if try_insert { + // Insert HEAD to the store and initialize header-sub. + // Normal insertion checks still apply here. + store.insert(network_head.clone()).await?; + } Ok(network_head) } @@ -898,6 +912,50 @@ mod tests { p2p_mock.expect_no_cmd().await; } + #[async_test] + async fn all_peers_disconnected_and_no_network_head_progress() { + let mut gen = ExtendedHeaderGenerator::new_from_height(30); + + let header30 = gen.next(); + + // Start Syncer and report height 30 as HEAD + let (syncer, store, mut p2p_mock) = initialized_syncer(header30.clone()).await; + + // Wait for the request but do not reply to it. + handle_session_batch(&mut p2p_mock, &[], 1..=29, false).await; + + p2p_mock.announce_all_peers_disconnected(); + // Syncer is now back to `connecting_event_loop`. + p2p_mock.expect_no_cmd().await; + + // Accounce a non-trusted peer. Syncer in `connecting_event_loop` can progress only + // if a trusted peer is connected. + p2p_mock.announce_peer_connected(); + p2p_mock.expect_no_cmd().await; + + // Accounce a trusted peer. + p2p_mock.announce_trusted_peer_connected(); + + // Now syncer will send request for HEAD. + let (height, amount, respond_to) = p2p_mock.expect_header_request_for_height_cmd().await; + assert_eq!(height, 0); + assert_eq!(amount, 1); + + // Report the same HEAD as before. + respond_to.send(Ok(vec![header30.clone()])).unwrap(); + assert_syncing(&syncer, &store, &[30..=30], 30).await; + + // Syncer initializes HeaderSub with the latest HEAD. + let head_from_syncer = p2p_mock.expect_init_header_sub().await; + assert_eq!(head_from_syncer, header30); + + // Syncer now is in `connected_event_loop` and will try to sync the gap + handle_session_batch(&mut p2p_mock, &[], 1..=29, false).await; + + p2p_mock.announce_all_peers_disconnected(); + p2p_mock.expect_no_cmd().await; + } + #[async_test] async fn non_contiguous_response() { let mut gen = ExtendedHeaderGenerator::new();