Repository: FuelLabs/fuel-core
Pull request #2978: fix(compression_service): prevent syncing from genesis if compression db is empty
Author: login: rymnc
Labels: name: no changelog
description: Skip the CI check of the changelog modification
Created: 2025-04-24T19:32:21Z | Merged: 2025-04-28T18:02:45Z

improving error handling, and refining synchronization logic. Key changes include adding an override for the starting block height, improving synchronization methods, and updating error reporting.

### Enhancements to Configurability:
* Added an `override_starting_height` field to `DaCompressionConfig` and `CompressionConfig`, allowing the service to start syncing from a specific block height. (`crates/fuel-core/src/service/config.rs` [[1]](diffhunk://#diff-184f5e785485e92ad26d50d0bbca115a93cd4e76d7d0bafb7052225eb31f0245R366) `crates/services/compression/src/config.rs` [[2]](diffhunk://#diff-5d0b8937548df3be41160e9752c4d383b66f498014e123054aa2ec486b93967aL1-R23) [[3]](diffhunk://#diff-5d0b8937548df3be41160e9752c4d383b66f498014e123054aa2ec486b93967aR37-R41)

### Improvements to Synchronization Logic:
* Updated the `sync_previously_produced_blocks` method to support early exit and handle the `override_starting_height` configuration. (`crates/services/compression/src/service.rs` [crates/services/compression/src/service.rsL224-R259](diffhunk://#diff-9fc61045febb6284fa8e01c5c33172787228fe21b8b813c62c8f03d2fec55fe8L224-R259))
* Modified the `await_synced` method to `await_synced_until`, enabling synchronization up to a specified block height. (`crates/fuel-core/src/service.rs` [[1]](diffhunk://#diff-57d6c5d4dadbd1198f75f2070852e4c27a251e756ed40fb6c214b4d78e4a1ecbL232-R242) `crates/services/compression/src/service.rs` [[2]](diffhunk://#diff-9fc61045febb6284fa8e01c5c33172787228fe21b8b813c62c8f03d2fec55fe8L274-R306)

### Error Handling Enhancements:
* Updated `get_block` methods in various components to return `anyhow::Result` instead of `Option`, providing more detailed error information. (`crates/fuel-core/src/service/adapters/compression_adapters.rs` [[1]](diffhunk://#diff-9e66cb09255fa99a40c07b24f3002b040d023c09b9469521b2c8ad8736bf33bfL63-R77) `crates/services/compression/src/ports/block_source.rs` [[2]](diffhunk://#diff-b35ece6e136e2fe3793da56f4456d50692646e9d371946e26024ada643151182L76-R76) `crates/services/compression/src/service.rs` [[3]](diffhunk://#diff-9fc61045febb6284fa8e01c5c33172787228fe21b8b813c62c8f03d2fec55fe8L247-R278)

### Test Suite Updates:
* Added new test cases to validate the behavior of the `override_starting_height` configuration and ensure proper synchronization logic. (`crates/services/compression/src/service.rs` [[1]](diffhunk://#diff-9fc61045febb6284fa8e01c5c33172787228fe21b8b813c62c8f03d2fec55fe8L607-R665) [[2]](diffhunk://#diff-9fc61045febb6284fa8e01c5c33172787228fe21b8b813c62c8f03d2fec55fe8L638-R739)

### Miscellaneous Changes:
* Improved error messages across the codebase to include more context, such as the specific block height or initialization errors. (`crates/services/src/service.rs` [[1]](diffhunk://#diff-daa67623825920672e831440a58a430d60cd713481c0e3b956292030162f569cL405-R405) `crates/services/compression/src/service.rs` [[2]](diffhunk://#diff-9fc61045febb6284fa8e01c5c33172787228fe21b8b813c62c8f03d2fec55fe8L247-R278)


Diff:
diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs
index d622de6ba57..1efeabfeb8b 100644
--- a/bin/fuel-core/src/cli/run.rs
+++ b/bin/fuel-core/src/cli/run.rs
@@ -82,7 +82,10 @@ use rlimit::{
 use std::{
     env,
     net,
-    num::NonZeroU64,
+    num::{
+        NonZeroU32,
+        NonZeroU64,
+    },
     path::PathBuf,
     str::FromStr,
     time::Duration,
@@ -238,6 +241,10 @@ pub struct Command {
     #[arg(long = "da-compression", env)]
     pub da_compression: Option<humantime::Duration>,
 
+    /// The starting height of the compression service.
+    #[arg(long = "da-compression-starting-height", env)]
+    pub da_compression_starting_height: Option<NonZeroU32>,
+
     /// A new block is produced instantly when transactions are available.
     #[clap(flatten)]
     pub poa_trigger: PoATriggerArgs,
@@ -341,6 +348,7 @@ impl Command {
             #[cfg(feature = "aws-kms")]
             consensus_aws_kms,
             da_compression,
+            da_compression_starting_height,
             poa_trigger,
             predefined_blocks_path,
             coinbase_recipient,
@@ -551,6 +559,7 @@ impl Command {
             Some(retention_duration) => DaCompressionMode::Enabled(
                 fuel_core::service::config::DaCompressionConfig {
                     retention_duration: retention_duration.into(),
+                    starting_height: da_compression_starting_height,
                     metrics: metrics.is_enabled(Module::Compression),
                 },
             ),
diff --git a/bin/fuel-core/src/cli/run/p2p.rs b/bin/fuel-core/src/cli/run/p2p.rs
index ea82a7b8078..c1757f3979b 100644
--- a/bin/fuel-core/src/cli/run/p2p.rs
+++ b/bin/fuel-core/src/cli/run/p2p.rs
@@ -237,6 +237,7 @@ impl KeypairArg {
             return Ok(KeypairArg::InlineSecret(secret))
         }
         let path = PathBuf::from_str(s);
+        #[allow(irrefutable_let_patterns)]
         if let Ok(pathbuf) = path {
             if pathbuf.exists() {
                 return Ok(KeypairArg::Path(pathbuf))
diff --git a/crates/fuel-core/src/service.rs b/crates/fuel-core/src/service.rs
index ffe43c7517e..314d2ca6e50 100644
--- a/crates/fuel-core/src/service.rs
+++ b/crates/fuel-core/src/service.rs
@@ -40,7 +40,10 @@ use fuel_core_storage::{
     IsNotFound,
     StorageAsMut,
 };
-use fuel_core_types::blockchain::consensus::Consensus;
+use fuel_core_types::{
+    blockchain::consensus::Consensus,
+    fuel_types::BlockHeight,
+};
 
 use crate::{
     combined_database::{
@@ -229,10 +232,14 @@ impl FuelService {
         Ok(())
     }
 
-    /// Wait for the compression service to be in sync with L2 height
-    pub async fn await_compression_synced(&self) -> anyhow::Result<()> {
+    /// Waits until the compression service has synced
+    /// with the given block height
+    pub async fn await_compression_synced_until(
+        &self,
+        block_height: &BlockHeight,
+    ) -> anyhow::Result<()> {
         if let Some(sync_observer) = &self.runner.shared.compression {
-            sync_observer.await_synced().await?;
+            sync_observer.await_synced_until(block_height).await?;
         }
         Ok(())
     }
diff --git a/crates/fuel-core/src/service/adapters/compression_adapters.rs b/crates/fuel-core/src/service/adapters/compression_adapters.rs
index 21935d4cb03..6ce7c982260 100644
--- a/crates/fuel-core/src/service/adapters/compression_adapters.rs
+++ b/crates/fuel-core/src/service/adapters/compression_adapters.rs
@@ -60,10 +60,9 @@ impl block_source::BlockSource for CompressionBlockImporterAdapter {
         self.block_importer.events_shared_result()
     }
 
-    fn get_block(&self, height: BlockAt) -> Option<SharedImportResult> {
+    fn get_block(&self, height: BlockAt) -> anyhow::Result<SharedImportResult> {
         self.import_result_provider_adapter
             .result_at_height(height.into())
-            .ok()
     }
 }
 
@@ -71,7 +70,11 @@ impl configuration::CompressionConfigProvider
     for crate::service::config::DaCompressionConfig
 {
     fn config(&self) -> config::CompressionConfig {
-        config::CompressionConfig::new(self.retention_duration, self.metrics)
+        config::CompressionConfig::new(
+            self.retention_duration,
+            self.starting_height,
+            self.metrics,
+        )
     }
 }
 
diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs
index 35a25a592bf..9f9d4c35555 100644
--- a/crates/fuel-core/src/service/config.rs
+++ b/crates/fuel-core/src/service/config.rs
@@ -1,6 +1,9 @@
 use clap::ValueEnum;
 use std::{
-    num::NonZeroU64,
+    num::{
+        NonZeroU32,
+        NonZeroU64,
+    },
     path::PathBuf,
     time::Duration,
 };
@@ -360,6 +363,7 @@ impl GasPriceConfig {
 pub struct DaCompressionConfig {
     pub retention_duration: Duration,
     pub metrics: bool,
+    pub starting_height: Option<NonZeroU32>,
 }
 
 #[derive(Debug, Clone)]
diff --git a/crates/services/compression/src/config.rs b/crates/services/compression/src/config.rs
index 7f7230b4b67..f878100ee98 100644
--- a/crates/services/compression/src/config.rs
+++ b/crates/services/compression/src/config.rs
@@ -1,17 +1,26 @@
-use std::time::Duration;
+use std::{
+    num::NonZeroU32,
+    time::Duration,
+};
 
 /// Compression configuration
 #[derive(Debug, Clone, Copy)]
 pub struct CompressionConfig {
     temporal_registry_retention: Duration,
+    starting_height: Option<NonZeroU32>,
     metrics: bool,
 }
 
 impl CompressionConfig {
     /// Create a new compression configuration
-    pub fn new(temporal_registry_retention: Duration, metrics: bool) -> Self {
+    pub fn new(
+        temporal_registry_retention: Duration,
+        starting_height: Option<NonZeroU32>,
+        metrics: bool,
+    ) -> Self {
         Self {
             temporal_registry_retention,
+            starting_height,
             metrics,
         }
     }
@@ -25,6 +34,11 @@ impl CompressionConfig {
     pub fn metrics(&self) -> bool {
         self.metrics
     }
+
+    /// Get the override starting height
+    pub fn starting_height(&self) -> Option<u32> {
+        self.starting_height.map(|height| height.get())
+    }
 }
 
 impl From<&CompressionConfig> for fuel_core_compression::Config {
diff --git a/crates/services/compression/src/ports/block_source.rs b/crates/services/compression/src/ports/block_source.rs
index 7ee51be6677..475c08d2040 100644
--- a/crates/services/compression/src/ports/block_source.rs
+++ b/crates/services/compression/src/ports/block_source.rs
@@ -73,5 +73,5 @@ pub trait BlockSource: Send + Sync {
     /// Should provide a stream of blocks with metadata
     fn subscribe(&self) -> BlockStream;
     /// Should provide the block at a given height
-    fn get_block(&self, height: BlockAt) -> Option<BlockWithMetadata>;
+    fn get_block(&self, height: BlockAt) -> anyhow::Result<BlockWithMetadata>;
 }
diff --git a/crates/services/compression/src/service.rs b/crates/services/compression/src/service.rs
index ee798ca912e..312f27c3cab 100644
--- a/crates/services/compression/src/service.rs
+++ b/crates/services/compression/src/service.rs
@@ -215,39 +215,88 @@ where
     }
 }
 
+#[derive(Debug)]
+enum SyncHeight {
+    StorageHeight(u32),
+    ConfiguredHeight(u32),
+    Genesis,
+}
+
+impl SyncHeight {
+    #[inline]
+    const fn value(&self) -> u32 {
+        match self {
+            SyncHeight::StorageHeight(height) => *height,
+            SyncHeight::ConfiguredHeight(height) => *height,
+            SyncHeight::Genesis => 0,
+        }
+    }
+
+    #[inline]
+    const fn is_from_storage(&self) -> bool {
+        matches!(self, SyncHeight::StorageHeight(_))
+    }
+}
+
 impl<B, S, CH> UninitializedCompressionService<B, S, CH>
 where
     B: BlockSource,
     S: CompressionStorage + LatestHeight,
     CH: CanonicalHeight,
 {
-    async fn sync_previously_produced_blocks(&mut self) -> crate::Result<()> {
-        let canonical_height = self.canonical_height.get();
+    async fn sync_previously_produced_blocks(
+        &mut self,
+        state_watcher: &StateWatcher,
+    ) -> crate::Result<()> {
         loop {
-            let storage_height = self.storage.latest_height();
+            // allows early exit if the service is stopping
+            let state = state_watcher.borrow();
+            if !state.starting() {
+                break;
+            }
+
+            let canonical_height = match self.canonical_height.get() {
+                Some(height) => height,
+                None => {
+                    // fuel-core started for first time,
+                    // no need to backfill blocks
+                    break;
+                }
+            };
+
+            let maybe_height =
+                match (self.storage.latest_height(), self.config.starting_height()) {
+                    (Some(height), _) => SyncHeight::StorageHeight(height),
+                    (None, Some(height)) => SyncHeight::ConfiguredHeight(height),
+                    (None, None) => SyncHeight::Genesis,
+                };
 
-            if canonical_height < storage_height {
+            if canonical_height < maybe_height.value() {
+                tracing::error!("Canonical height is less than fetched height: Canonical height: {:?}, Fetched height: {:?}", &canonical_height, &maybe_height);
                 return Err(crate::errors::CompressionError::FailedToGetSyncStatus);
             }
 
-            if canonical_height == storage_height {
+            if canonical_height == maybe_height.value() && maybe_height.is_from_storage()
+            {
+                tracing::info!("Compression database is up to date");
                 break;
             }
 
-            let next_block_height = storage_height.map(|height| height.saturating_add(1));
-
-            let next_block_height = match next_block_height {
-                Some(block_height) => BlockAt::Specific(block_height),
-                None => BlockAt::Genesis,
+            let height_to_sync = match maybe_height {
+                SyncHeight::Genesis => BlockAt::Genesis,
+                SyncHeight::StorageHeight(height) => {
+                    BlockAt::Specific(height.saturating_add(1))
+                }
+                SyncHeight::ConfiguredHeight(height) => BlockAt::Specific(height),
             };
 
-            let block_with_metadata = self
-                .block_source
-                .get_block(next_block_height)
-                .ok_or(crate::errors::CompressionError::FailedToGetBlock(format!(
-                    "during synchronization of canonical chain at height: {:?}",
-                    next_block_height
-                )))?;
+            let block_with_metadata =
+                self.block_source.get_block(height_to_sync).map_err(|err| {
+                    crate::errors::CompressionError::FailedToGetBlock(format!(
+                        "during synchronization of canonical chain at height: {:?}: {}",
+                        height_to_sync, err
+                    ))
+                })?;
 
             handle_new_block(
                 &mut self.storage,
@@ -271,11 +320,11 @@ pub struct SharedData {
 
 impl SharedData {
     /// Waits until the compression service has synced
-    /// with current l2 block height
-    pub async fn await_synced(&self) -> crate::Result<()> {
+    /// with the given block height
+    pub async fn await_synced_until(&self, block_height: &u32) -> crate::Result<()> {
         let mut observer = self.sync_observer.clone();
         loop {
-            if observer.borrow_and_update().is_synced() {
+            if observer.borrow_and_update().is_synced_until(block_height) {
                 break;
             }
 
@@ -309,10 +358,10 @@ where
 
     async fn into_task(
         mut self,
-        _state_watcher: &StateWatcher,
+        state_watcher: &StateWatcher,
         _params: Self::TaskParams,
     ) -> anyhow::Result<Self::Task> {
-        self.sync_previously_produced_blocks().await?;
+        self.sync_previously_produced_blocks(state_watcher).await?;
 
         let compression_service = CompressionService::new(
             self.block_source.subscribe(),
@@ -362,7 +411,7 @@ where
 
     async fn shutdown(mut self) -> anyhow::Result<()> {
         // gracefully handle all the remaining blocks in the stream and then stop
-        while let Some(block_with_metadata) = self.block_stream.next().await {
+        if let Some(Some(block_with_metadata)) = self.block_stream.next().now_or_never() {
             if let Err(e) = self.handle_new_block(&block_with_metadata) {
                 return Err(anyhow::anyhow!(e).context(format!(
                     "Couldn't compress block: {}. Shutting down. \
@@ -401,6 +450,8 @@ where
 #[allow(non_snake_case)]
 #[cfg(test)]
 mod tests {
+    use std::num::NonZeroU32;
+
     use super::*;
     use crate::{
         ports::block_source::{
@@ -443,8 +494,8 @@ mod tests {
         fn get_block(
             &self,
             _: crate::ports::block_source::BlockAt,
-        ) -> Option<BlockWithMetadata> {
-            None
+        ) -> anyhow::Result<BlockWithMetadata> {
+            anyhow::bail!("Block not found")
         }
     }
 
@@ -475,11 +526,18 @@ mod tests {
         fn default() -> Self {
             Self(crate::config::CompressionConfig::new(
                 std::time::Duration::from_secs(10),
+                None,
                 false,
             ))
         }
     }
 
+    impl MockConfigProvider {
+        fn new(config: crate::config::CompressionConfig) -> Self {
+            Self(config)
+        }
+    }
+
     impl CompressionConfigProvider for MockConfigProvider {
         fn config(&self) -> crate::config::CompressionConfig {
             self.0
@@ -497,7 +555,10 @@ mod tests {
 
     impl CanonicalHeight for MockCanonicalHeightProvider {
         fn get(&self) -> Option<u32> {
-            Some(self.0)
+            match self.0 {
+                0 => None,
+                _ => Some(self.0),
+            }
         }
     }
 
@@ -541,11 +602,12 @@ mod tests {
         fn get_block(
             &self,
             height: crate::ports::block_source::BlockAt,
-        ) -> Option<BlockWithMetadata> {
+        ) -> anyhow::Result<BlockWithMetadata> {
             self.0
                 .iter()
                 .find(|block| height == *block.height())
                 .cloned()
+                .ok_or_else(|| anyhow::anyhow!("Block not found"))
         }
     }
 
@@ -576,6 +638,7 @@ mod tests {
             .get(&0.into())
             .unwrap();
         assert!(maybe_block.is_none());
+        service.shutdown().await.unwrap();
     }
 
     #[tokio::test]
@@ -604,20 +667,23 @@ mod tests {
         let _ = service.run(&mut StateWatcher::started()).await;
 
         // then
-        sync_observer.await_synced().await.unwrap();
+        let target_block_height = 0;
+        sync_observer
+            .await_synced_until(&target_block_height)
+            .await
+            .unwrap();
         let maybe_block = service
             .storage
             .storage_as_ref::<storage::CompressedBlocks>()
-            .get(&0.into())
+            .get(&target_block_height.into())
             .unwrap();
         assert!(maybe_block.is_some());
+        service.shutdown().await.unwrap();
     }
 
     #[tokio::test]
-    async fn compression_service__can_resync_with_canonical_height() {
-        // given
-        // we provide a block source with some old blocks,
-        // and a canonical height provider with a height of 5
+    async fn compression_service__syncs_from_scratch_when_database_is_empty() {
+        // given: we start the compression service, with a canonical height provider of height 5
         let block_count = 10;
         let mut blocks = Vec::with_capacity(block_count);
         for i in 0..u32::try_from(block_count).unwrap() {
@@ -635,18 +701,62 @@ mod tests {
             canonical_height_provider.clone(),
         );
 
-        // when
+        // when: the syncing with canonical height provider occurs
         let service = uninit_service
-            .into_task(&Default::default(), ())
+            .into_task(&StateWatcher::starting(), ())
             .await
             .unwrap();
 
-        // then
+        // then: it has a compressed block for the canonical height
         let maybe_block = service
             .storage
             .storage_as_ref::<storage::CompressedBlocks>()
             .get(&canonical_height_provider.get().unwrap().into())
             .unwrap();
         assert!(maybe_block.is_some());
+        service.shutdown().await.unwrap();
+    }
+
+    #[tokio::test]
+    async fn compression_service__syncs_from_overridden_starting_height_when_provided() {
+        // given: we start the compression service, with a canonical height provider of height 5,
+        // and a config override of starting height 1
+        let block_count = 10;
+        let override_starting_height = 1;
+        let mut blocks = Vec::with_capacity(block_count);
+        for i in 0..u32::try_from(block_count).unwrap() {
+            blocks.push(BlockWithMetadata::test_block_with_height(i));
+        }
+        let block_source = MockBlockSource::new(blocks);
+        let storage = test_storage();
+        let config_provider =
+            MockConfigProvider::new(crate::config::CompressionConfig::new(
+                std::time::Duration::from_secs(10),
+                Some(NonZeroU32::new(override_starting_height).unwrap()),
+                false,
+            ));
+        let canonical_height_provider = MockCanonicalHeightProvider::new(5);
+
+        let uninit_service = UninitializedCompressionService::new(
+            block_source,
+            storage,
+            config_provider.config(),
+            canonical_height_provider.clone(),
+        );
+
+        // when: the syncing with canonical height provider occurs
+        let service = uninit_service
+            .into_task(&StateWatcher::starting(), ())
+            .await
+            .unwrap();
+
+        // then: it has a block for the overridden starting height
+        let maybe_block = service
+            .storage
+            .storage_as_ref::<storage::CompressedBlocks>()
+            .get(&override_starting_height.into())
+            .unwrap();
+        assert!(maybe_block.is_some());
+        service.shutdown().await.unwrap();
     }
 }
diff --git a/crates/services/compression/src/sync_state.rs b/crates/services/compression/src/sync_state.rs
index 8f0046b49a5..8e78a1eb0c9 100644
--- a/crates/services/compression/src/sync_state.rs
+++ b/crates/services/compression/src/sync_state.rs
@@ -12,8 +12,8 @@ pub enum SyncState {
 }
 
 impl SyncState {
-    pub(crate) fn is_synced(&self) -> bool {
-        matches!(self, Self::Synced(_))
+    pub(crate) fn is_synced_until(&self, height: &BlockHeight) -> bool {
+        matches!(self, Self::Synced(h) if h >= height)
     }
 }
 
diff --git a/crates/services/src/service.rs b/crates/services/src/service.rs
index aa0f7c68ed8..5bc995df526 100644
--- a/crates/services/src/service.rs
+++ b/crates/services/src/service.rs
@@ -402,7 +402,7 @@ async fn run<S>(
     let mut task = service
         .into_task(&state, params)
         .await
-        .unwrap_or_else(|_| panic!("The initialization of {} failed", S::NAME));
+        .unwrap_or_else(|e| panic!("The initialization of {} failed: {}", S::NAME, e));
 
     sender.send_if_modified(|s| {
         if s.starting() {
diff --git a/crates/services/src/state.rs b/crates/services/src/state.rs
index fb7505a196a..e707f75d993 100644
--- a/crates/services/src/state.rs
+++ b/crates/services/src/state.rs
@@ -68,6 +68,13 @@ impl StateWatcher {
         core::mem::forget(sender);
         Self(receiver)
     }
+
+    /// Create a new `StateWatcher` with the `State::Starting` state.
+    pub fn starting() -> Self {
+        let (sender, receiver) = watch::channel(State::Starting);
+        core::mem::forget(sender);
+        Self(receiver)
+    }
 }
 
 impl StateWatcher {
diff --git a/tests/tests/da_compression.rs b/tests/tests/da_compression.rs
index c7f9224593e..c6ce3d6aaf7 100644
--- a/tests/tests/da_compression.rs
+++ b/tests/tests/da_compression.rs
@@ -38,6 +38,7 @@ use fuel_core_types::{
         Input,
         UniqueIdentifier,
     },
+    fuel_types::BlockHeight,
     secrecy::Secret,
     signer::SignMode,
 };
@@ -52,6 +53,7 @@ use test_helpers::{
         SigningAccount,
     },
     config_with_fee,
+    fuel_core_driver::FuelCoreDriver,
 };
 
 #[tokio::test]
@@ -63,6 +65,7 @@ async fn can_fetch_da_compressed_block_from_graphql() {
     config.consensus_signer = SignMode::Key(Secret::new(poa_secret.into()));
     let compression_config = DaCompressionConfig {
         retention_duration: Duration::from_secs(3600),
+        starting_height: None,
         metrics: false,
     };
     config.da_compression = DaCompressionMode::Enabled(compression_config.clone());
@@ -92,7 +95,9 @@ async fn can_fetch_da_compressed_block_from_graphql() {
             panic!("unexpected result {other:?}")
         }
     };
-    srv.await_compression_synced().await.unwrap();
+    srv.await_compression_synced_until(&block_height)
+        .await
+        .unwrap();
 
     let block = client
         .da_compressed_block(block_height)
@@ -153,6 +158,7 @@ async fn da_compressed_blocks_are_available_from_non_block_producing_nodes() {
     let mut config = Config::local_node();
     config.da_compression = DaCompressionMode::Enabled(DaCompressionConfig {
         retention_duration: Duration::from_secs(3600),
+        starting_height: None,
         metrics: false,
     });
 
@@ -178,7 +184,11 @@ async fn da_compressed_blocks_are_available_from_non_block_producing_nodes() {
     // Insert some txs
     let expected = producer.insert_txs().await;
     validator.consistency_20s(&expected).await;
-    validator.node.await_compression_synced().await.unwrap();
+    validator
+        .node
+        .await_compression_synced_until(&1u32.into())
+        .await
+        .unwrap();
 
     let block_height = 1u32.into();
 
@@ -189,3 +199,129 @@ async fn da_compressed_blocks_are_available_from_non_block_producing_nodes() {
         .expect("Compressed block not available from validator");
     let _: VersionedCompressedBlock = postcard::from_bytes(&compressed_block).unwrap();
 }
+
+#[tokio::test]
+async fn da_compression__starts_and_compresses_blocks_correctly_from_empty_database() {
+    // given: the node starts without compression enabled, and produces blocks
+    let args = vec!["--state-rewind-duration", "7d", "--debug"];
+    let driver = FuelCoreDriver::spawn(&args).await.unwrap();
+
+    let blocks_to_produce = 10;
+    let current_height = driver
+        .client
+        .produce_blocks(blocks_to_produce, None)
+        .await
+        .unwrap();
+    assert_eq!(current_height, blocks_to_produce.into());
+
+    let db = driver.kill().await;
+
+    // when: the node is restarted with compression enabled, and blocks are produced
+    let args = vec!["--da-compression", "7d", "--debug"];
+    let driver = FuelCoreDriver::spawn_with_directory(db, &args)
+        .await
+        .unwrap();
+
+    let current_height = driver
+        .client
+        .produce_blocks(blocks_to_produce, None)
+        .await
+        .unwrap();
+    assert_eq!(current_height, BlockHeight::from(blocks_to_produce * 2));
+
+    driver
+        .node
+        .await_compression_synced_until(&current_height)
+        .await
+        .unwrap();
+
+    // then: the da compressed blocks from genesis to height blocks_to_produce exist
+    for height in 0..=blocks_to_produce {
+        let compressed_block = driver
+            .client
+            .da_compressed_block(height.into())
+            .await
+            .unwrap();
+        assert!(
+            compressed_block.is_some(),
+            "DA compressed block at height {} is missing",
+            height
+        );
+    }
+
+    // teardown
+    driver.kill().await;
+}
+
+#[tokio::test]
+async fn da_compression__starts_and_compresses_blocks_correctly_with_overridden_height() {
+    // given: the node starts without compression enabled, and produces blocks
+    let args = vec!["--state-rewind-duration", "7d", "--debug"];
+    let driver = FuelCoreDriver::spawn(&args).await.unwrap();
+
+    let blocks_to_produce = 10;
+
+    let current_height = driver
+        .client
+        .produce_blocks(blocks_to_produce, None)
+        .await
+        .unwrap();
+    assert_eq!(current_height, blocks_to_produce.into());
+
+    let db = driver.kill().await;
+
+    // when: the node is restarted with compression enabled, starting height overridden, blocks are produced
+    let override_starting_height = 10;
+    let override_starting_height_str = format!("{}", override_starting_height);
+    let args = vec![
+        "--da-compression",
+        "7d",
+        "--da-compression-starting-height",
+        &override_starting_height_str,
+        "--debug",
+    ];
+    let driver = FuelCoreDriver::spawn_with_directory(db, &args)
+        .await
+        .unwrap();
+
+    let current_height = driver
+        .client
+        .produce_blocks(blocks_to_produce, None)
+        .await
+        .unwrap();
+    assert_eq!(current_height, BlockHeight::from(blocks_to_produce * 2));
+
+    driver
+        .node
+        .await_compression_synced_until(&current_height)
+        .await
+        .unwrap();
+
+    // then: the da compressed blocks from height 0 to height override_starting_height don't exist
+    // and the da compressed blocks from height override_starting_height to height blocks_to_produce * 2 exist
+    for height in 0..override_starting_height {
+        let compressed_block = driver
+            .client
+            .da_compressed_block(height.into())
+            .await
+            .unwrap();
+        assert!(
+            compressed_block.is_none(),
+            "DA compressed block at height {} is present",
+            height
+        );
+    }
+
+    for height in override_starting_height..=blocks_to_produce * 2 {
+        let compressed_block = driver
+            .client
+            .da_compressed_block(height.into())
+            .await
+            .unwrap();
+        assert!(
+            compressed_block.is_some(),
+            "DA compressed block at height {} is missing",
+            height
+        );
+    }
+}


Reviews:
user: login: rymnc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T19:40:26Z
user: login: AurelienFT
body: Testing coverage is so nice ! Really comprehensive scenarios
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T12:16:52Z
user: login: segfault-magnet
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T14:37:11Z
user: login: xgreenx
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-25T18:43:03Z
user: login: xgreenx
body: What if `starting_height` is more than current `on_chain` database? I think compression should skip blocks then=)
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-25T18:49:45Z
user: login: rymnc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-27T22:08:03Z
user: login: rymnc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T03:37:05Z
user: login: rymnc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T03:37:10Z
user: login: rymnc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T03:37:16Z
user: login: xgreenx
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-28T18:02:32Z

Review comments:
diff_hunk: @@ -189,3 +195,63 @@ async fn da_compressed_blocks_are_available_from_non_block_producing_nodes() {
         .expect("Compressed block not available from validator");
     let _: VersionedCompressedBlock = postcard::from_bytes(&compressed_block).unwrap();
 }
+
+#[tokio::test]
+async fn da_compression__starts_and_compresses_blocks_correctly_from_empty_database() {
path: tests/tests/da_compression.rs
user: login: rymnc
body: this test fails on 0.43.1
created_at: 2025-04-24T19:40:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -362,7 +390,7 @@ where
 
     async fn shutdown(mut self) -> anyhow::Result<()> {
         // gracefully handle all the remaining blocks in the stream and then stop
-        while let Some(block_with_metadata) = self.block_stream.next().await {
+        if let Some(Some(block_with_metadata)) = self.block_stream.next().now_or_never() {
path: crates/services/compression/src/service.rs
user: login: segfault-magnet
body: If we're in the process of shutting down, why not rely that the stream will also get the shutdown signal at some point and close on its own?

Is potentially swallowing up an update or two here ok when shutting down?
created_at: 2025-04-25T14:34:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -238,6 +241,10 @@ pub struct Command {
     #[arg(long = "da-compression", env)]
     pub da_compression: Option<humantime::Duration>,
 
+    /// Override and resync the compression service from the given starting height.
+    #[arg(long = "da-compression-override-starting-height", env)]
+    pub da_compression_override_starting_height: Option<NonZeroU32>,
path: bin/fuel-core/src/cli/run.rs
user: login: xgreenx
body: ```suggestion
    /// The starting height of the compression service.
    #[arg(long = "da-compression-starting-height", env)]
    pub da_compression_starting_height: Option<NonZeroU32>,
```
created_at: 2025-04-25T18:38:56Z
author_association: COLLABORATOR
diff_hunk: @@ -221,16 +221,42 @@ where
     S: CompressionStorage + LatestHeight,
     CH: CanonicalHeight,
 {
-    async fn sync_previously_produced_blocks(&mut self) -> crate::Result<()> {
-        let canonical_height = self.canonical_height.get();
+    async fn sync_previously_produced_blocks(
+        &mut self,
+        state_watcher: &StateWatcher,
+    ) -> crate::Result<()> {
+        let mut overridden = false;
         loop {
-            let storage_height = self.storage.latest_height();
+            // allows early exit if the service is stopping
+            let state = state_watcher.borrow();
+            if state.stopping() || state.stopped() {
path: crates/services/compression/src/service.rs
user: login: xgreenx
body: ```suggestion
            if !state.starting() {
```
created_at: 2025-04-25T18:41:12Z
author_association: COLLABORATOR
diff_hunk: @@ -221,16 +221,42 @@ where
     S: CompressionStorage + LatestHeight,
     CH: CanonicalHeight,
 {
-    async fn sync_previously_produced_blocks(&mut self) -> crate::Result<()> {
-        let canonical_height = self.canonical_height.get();
+    async fn sync_previously_produced_blocks(
+        &mut self,
+        state_watcher: &StateWatcher,
+    ) -> crate::Result<()> {
+        let mut overridden = false;
         loop {
-            let storage_height = self.storage.latest_height();
+            // allows early exit if the service is stopping
+            let state = state_watcher.borrow();
+            if state.stopping() || state.stopped() {
+                break;
+            }
+
+            let canonical_height = self.canonical_height.get();
+
+            let storage_height =
+                match (self.config.override_starting_height(), overridden) {
+                    (Some(height), false) => {
+                        overridden = true;
+                        Some(height.saturating_sub(1))
+                    }
+                    (_, _) => self.storage.latest_height(),
+                };
+
+            if storage_height.is_none() {
+                // if the storage height is unavailable, don't execute blocks from genesis
+                tracing::info!("Compression database started from scratch");
+                break;
+            }
path: crates/services/compression/src/service.rs
user: login: xgreenx
body: We only need to use `self.config.starting_height()` height, if `self.storage.latest_height()` is `None`.

If `self.config.starting_height()` is `None` as well, then we should use genesis block.
created_at: 2025-04-25T18:42:47Z
author_association: COLLABORATOR
diff_hunk: @@ -189,3 +195,63 @@ async fn da_compressed_blocks_are_available_from_non_block_producing_nodes() {
         .expect("Compressed block not available from validator");
     let _: VersionedCompressedBlock = postcard::from_bytes(&compressed_block).unwrap();
 }
+
+#[tokio::test]
+async fn da_compression__starts_and_compresses_blocks_correctly_from_empty_database() {
path: tests/tests/da_compression.rs
user: login: xgreenx
body: It would be nice if this test was CLI based=)
created_at: 2025-04-25T18:46:41Z
author_association: COLLABORATOR
diff_hunk: @@ -362,7 +390,7 @@ where
 
     async fn shutdown(mut self) -> anyhow::Result<()> {
         // gracefully handle all the remaining blocks in the stream and then stop
-        while let Some(block_with_metadata) = self.block_stream.next().await {
+        if let Some(Some(block_with_metadata)) = self.block_stream.next().now_or_never() {
path: crates/services/compression/src/service.rs
user: login: rymnc
body: yeah its fine
created_at: 2025-04-27T22:08:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -221,16 +221,42 @@ where
     S: CompressionStorage + LatestHeight,
     CH: CanonicalHeight,
 {
-    async fn sync_previously_produced_blocks(&mut self) -> crate::Result<()> {
-        let canonical_height = self.canonical_height.get();
+    async fn sync_previously_produced_blocks(
+        &mut self,
+        state_watcher: &StateWatcher,
+    ) -> crate::Result<()> {
+        let mut overridden = false;
         loop {
-            let storage_height = self.storage.latest_height();
+            // allows early exit if the service is stopping
+            let state = state_watcher.borrow();
+            if state.stopping() || state.stopped() {
path: crates/services/compression/src/service.rs
user: login: rymnc
body: addressed in 9bdb29aab1
created_at: 2025-04-28T03:37:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -238,6 +241,10 @@ pub struct Command {
     #[arg(long = "da-compression", env)]
     pub da_compression: Option<humantime::Duration>,
 
+    /// Override and resync the compression service from the given starting height.
+    #[arg(long = "da-compression-override-starting-height", env)]
+    pub da_compression_override_starting_height: Option<NonZeroU32>,
path: bin/fuel-core/src/cli/run.rs
user: login: rymnc
body: addressed in 9bdb29aab1
created_at: 2025-04-28T03:37:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -221,16 +221,42 @@ where
     S: CompressionStorage + LatestHeight,
     CH: CanonicalHeight,
 {
-    async fn sync_previously_produced_blocks(&mut self) -> crate::Result<()> {
-        let canonical_height = self.canonical_height.get();
+    async fn sync_previously_produced_blocks(
+        &mut self,
+        state_watcher: &StateWatcher,
+    ) -> crate::Result<()> {
+        let mut overridden = false;
         loop {
-            let storage_height = self.storage.latest_height();
+            // allows early exit if the service is stopping
+            let state = state_watcher.borrow();
+            if state.stopping() || state.stopped() {
+                break;
+            }
+
+            let canonical_height = self.canonical_height.get();
+
+            let storage_height =
+                match (self.config.override_starting_height(), overridden) {
+                    (Some(height), false) => {
+                        overridden = true;
+                        Some(height.saturating_sub(1))
+                    }
+                    (_, _) => self.storage.latest_height(),
+                };
+
+            if storage_height.is_none() {
+                // if the storage height is unavailable, don't execute blocks from genesis
+                tracing::info!("Compression database started from scratch");
+                break;
+            }
path: crates/services/compression/src/service.rs
user: login: rymnc
body: addressed in 9bdb29aab1
created_at: 2025-04-28T03:37:15Z
author_association: CONTRIBUTOR

PR comments:
user: login: rymnc
created_at: 2025-04-28T03:50:59Z
body: > It would be nice if this test was CLI based=)

was just following the other tests in the same file, but addressed in 0d8d3beea4
author_association: CONTRIBUTOR
user: login: rymnc
created_at: 2025-04-28T03:51:22Z
body: > What if starting_height is more than current on_chain database? I think compression should skip blocks then=)

we throw an error
author_association: CONTRIBUTOR