Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ pub(crate) async fn build_network_config(
)
.into(),
("--sync", "warp").into(),
("--blocks-pruning", "256").into(),
])
})
.with_node(|node| {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ async fn assert_warp_sync(node: &NetworkNode) -> Result<(), anyhow::Error> {
Ok(())
}

// Asserting Gap sync requires at least sync=debug level
async fn assert_gap_sync(node: &NetworkNode) -> Result<(), anyhow::Error> {
let option_1_line = LogLineCountOptions::new(|n| n == 1, Duration::from_secs(20), false);
// Asserting Gap sync requires at least sync=trace level
async fn assert_gap_sync(node: &NetworkNode, is_archive: bool) -> Result<(), anyhow::Error> {
let option_1_line = LogLineCountOptions::new(|n| n == 1, Duration::from_secs(5), false);
let option_at_least_5_lines =
LogLineCountOptions::new(|n| n >= 5, Duration::from_secs(20), false);

Expand Down Expand Up @@ -117,6 +117,27 @@ async fn assert_gap_sync(node: &NetworkNode) -> Result<(), anyhow::Error> {
return Err(anyhow!("Gap sync block imports are not started"));
}

// Verify body download behavior based on archive mode:
// - Archive nodes should download bodies (body: non-zero)
// - Non-archive nodes should skip bodies (body: 0 B)
let (body_pattern, body_error) = if is_archive {
(
r"(?<!\[Parachain\] )Gap sync cumulative stats:.*body: [1-9]",
"archive node should download bodies",
)
} else {
(
r"(?<!\[Parachain\] )Gap sync cumulative stats:.*body: 0 B",
"non-archive node should not download bodies",
)
};
let result = node
.wait_log_line_count_with_timeout(body_pattern, false, option_at_least_5_lines.clone())
.await?;
if !result.success() {
return Err(anyhow!("Gap sync: {body_error}"));
}

let result = node
.wait_log_line_count_with_timeout(
r"(?<!\[Parachain\] )Imported [0-9]+ out of [0-9]+ blocks.*\(origin: GapSync\)",
Expand Down Expand Up @@ -160,9 +181,10 @@ async fn full_node_warp_sync() -> Result<(), anyhow::Error> {

// Assert warp and gap syncs only for relaychain.
// "five" is not warp syncing the relaychain
for name in ["dave", "eve", "four"] {
// dave is non-archive (--blocks-pruning 256), eve and four use default (archive-canonical)
for (name, is_archive) in [("dave", false), ("eve", true), ("four", true)] {
assert_warp_sync(network.get_node(name)?).await?;
assert_gap_sync(network.get_node(name)?).await?;
assert_gap_sync(network.get_node(name)?, is_archive).await?;
}

// Check relaychain progress
Expand Down Expand Up @@ -204,9 +226,9 @@ async fn full_node_warp_sync() -> Result<(), anyhow::Error> {
log::info!("Waiting for ferdie to be up");
network.get_node("ferdie")?.wait_until_is_up(60u64).await?;

// Assert warp and gap sync for ferdie
// Assert warp and gap sync for ferdie (uses default archive-canonical)
assert_warp_sync(network.get_node("ferdie")?).await?;
assert_gap_sync(network.get_node("ferdie")?).await?;
assert_gap_sync(network.get_node("ferdie")?, true).await?;

// Check progress for ferdie
log::info!("Checking full node ferdie is syncing");
Expand Down
22 changes: 22 additions & 0 deletions prdoc/pr_10752.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
title: ' Gap Sync: Skip Body Requests for Non-Archive Nodes'
doc:
- audience: Node Operator
description: |-
### Summary
This PR optimizes gap sync bandwidth usage by skipping body requests for non-archive nodes. Bodies are unnecessary during gap sync when the node doesn't maintain full block history, while archive nodes continue to request bodies to preserve complete history.
It reduces bandwidth consumption and database size significantly for typical validator/full nodes.

Additionally added some gap sync statistics for observability:
- Introduced `GapSyncStats` to track bandwidth usage: header bytes, body bytes, justification bytes
- Logged on gap sync completion to provide visibility into bandwidth savings
crates:
- name: sc-network-sync
bump: major
validate: false
- name: sc-service
bump: major
validate: false
- name: sc-client-db
bump: patch
- name: sc-cli
bump: patch
3 changes: 3 additions & 0 deletions substrate/client/cli/src/arg_enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ pub enum SyncMode {
/// Download blocks without executing them. Download latest state without proofs.
FastUnsafe,
/// Prove finality and download the latest state.
/// After warp sync completes, the node will have block headers but not bodies for historical
/// blocks (unless `blocks-pruning` is set to archive mode). This saves bandwidth while still
/// allowing the node to serve as a warp sync source for other nodes.
Warp,
}

Expand Down
4 changes: 4 additions & 0 deletions substrate/client/db/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1726,6 +1726,10 @@ impl<Block: BlockT> Backend<Block> {
}

let should_check_block_gap = !header_exists_in_db || !body_exists_in_db;
debug!(
target: "db",
"should_check_block_gap = {should_check_block_gap}",
);

if should_check_block_gap {
let update_gap =
Expand Down
Loading
Loading