Repository: starcoinorg/starcoin
Pull request #4447: Batch pull absent blocks
Author: login: jackzhhuang
Created: 2025-04-01T07:03:23Z | Merged: 2025-04-02T04:03:17Z

<!--- Please provide a general summary of your changes in the title above -->

## Pull request type

<!-- Please try to limit your pull request to one type, submit multiple pull requests if needed. -->

Please check the type of change your PR introduces:

- [ ] Bugfix
- [ ] Feature
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] Documentation content changes
- [ ] Other (please describe):

## What is the current behavior?

<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

Issue Number: N/A

## What is the new behavior?

<!-- Please describe the behavior or changes that are being added by this PR. -->

-
-
-

## Other information

<!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a unified capability to retrieve missing blocks by allowing clients to query blocks based on provided identifiers and expiration parameters.
  - Expanded network and asynchronous interfaces to support efficient batch retrieval of these blocks.
  - Added new methods and structures to handle requests and responses for absent blocks across various services.

- **Tests**
  - Added comprehensive test scenarios to validate the new batch retrieval and missing block querying functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/chain/api/src/message.rs b/chain/api/src/message.rs
index 0867a38e8f..04273eb5cb 100644
--- a/chain/api/src/message.rs
+++ b/chain/api/src/message.rs
@@ -77,6 +77,10 @@ pub enum ChainRequest {
         start_id: HashValue,
         end_id: Option<HashValue>,
     },
+    GetAbsentBlocks {
+        absent_id: Vec<HashValue>,
+        exp: u64,
+    },
 }
 
 impl ServiceRequest for ChainRequest {
@@ -110,4 +114,5 @@ pub enum ChainResponse {
     GhostdagDataOption(Box<Option<GhostdagData>>),
     IsAncestorOfCommand { reachability_view: ReachabilityView },
     GetRangeInLocation { range: RangeInLocation },
+    GetAbsentBlocks { absent_blocks: Vec<Block> },
 }
diff --git a/chain/api/src/service.rs b/chain/api/src/service.rs
index 776d977fb9..21988f9227 100644
--- a/chain/api/src/service.rs
+++ b/chain/api/src/service.rs
@@ -7,7 +7,10 @@ use anyhow::{bail, Result};
 use starcoin_crypto::HashValue;
 use starcoin_dag::consensusdb::consenses_state::{DagStateView, ReachabilityView};
 use starcoin_dag::types::ghostdata::GhostdagData;
-use starcoin_network_rpc_api::{GetRangeInLocationRequest, GetRangeInLocationResponse};
+use starcoin_network_rpc_api::{
+    GetAbsentBlockRequest, GetAbsentBlockResponse, GetRangeInLocationRequest,
+    GetRangeInLocationResponse,
+};
 use starcoin_service_registry::{ActorService, ServiceHandler, ServiceRef};
 use starcoin_types::contract_event::{ContractEvent, ContractEventInfo};
 use starcoin_types::filter::Filter;
@@ -164,6 +167,8 @@ pub trait ChainAsyncService:
         ancestor: HashValue,
         descendants: Vec<HashValue>,
     ) -> Result<starcoin_dag::consensusdb::consenses_state::ReachabilityView>;
+    async fn get_absent_blocks(&self, req: GetAbsentBlockRequest)
+        -> Result<GetAbsentBlockResponse>;
 }
 
 #[async_trait::async_trait]
@@ -502,6 +507,23 @@ where
         }
     }
 
+    async fn get_absent_blocks(
+        &self,
+        req: GetAbsentBlockRequest,
+    ) -> Result<GetAbsentBlockResponse> {
+        let response = self
+            .send(ChainRequest::GetAbsentBlocks {
+                absent_id: req.absent_id,
+                exp: req.exp,
+            })
+            .await??;
+        if let ChainResponse::GetAbsentBlocks { absent_blocks } = response {
+            Ok(GetAbsentBlockResponse { absent_blocks })
+        } else {
+            bail!("get range in location error");
+        }
+    }
+
     async fn get_dag_state(&self) -> Result<DagStateView> {
         let response = self.send(ChainRequest::GetDagStateView).await??;
         if let ChainResponse::DagStateView(dag_state) = response {
diff --git a/chain/service/src/chain_service.rs b/chain/service/src/chain_service.rs
index 86eab9f957..d177110163 100644
--- a/chain/service/src/chain_service.rs
+++ b/chain/service/src/chain_service.rs
@@ -1,7 +1,7 @@
 // Copyright (c) The Starcoin Core Contributors
 // SPDX-License-Identifier: Apache-2.0
 
-use anyhow::{format_err, Error, Result};
+use anyhow::{bail, format_err, Error, Result};
 use starcoin_chain::BlockChain;
 use starcoin_chain_api::message::{ChainRequest, ChainResponse};
 use starcoin_chain_api::range_locate::{self, RangeInLocation};
@@ -13,6 +13,7 @@ use starcoin_crypto::HashValue;
 use starcoin_dag::blockdag::BlockDAG;
 use starcoin_dag::consensusdb::consenses_state::DagStateView;
 use starcoin_dag::types::ghostdata::GhostdagData;
+use starcoin_dag::GetAbsentBlock;
 use starcoin_logger::prelude::*;
 use starcoin_service_registry::{
     ActorService, EventHandler, ServiceContext, ServiceFactory, ServiceHandler,
@@ -266,6 +267,11 @@ impl ServiceHandler<Self, ChainRequest> for ChainReaderService {
                     range: self.inner.get_range_in_location(start_id, end_id)?,
                 })
             }
+            ChainRequest::GetAbsentBlocks { absent_id, exp } => {
+                Ok(ChainResponse::GetAbsentBlocks {
+                    absent_blocks: self.inner.get_absent_blocks(absent_id, exp)?,
+                })
+            }
         }
     }
 }
@@ -325,6 +331,23 @@ impl ChainReaderServiceInner {
         )?;
         Ok(())
     }
+
+    fn get_absent_blocks(&self, absent_id: Vec<HashValue>, exp: u64) -> Result<Vec<Block>> {
+        let result = self
+            .dag
+            .get_absent_blocks(GetAbsentBlock { absent_id, exp })?;
+
+        result
+            .absent_blocks
+            .into_iter()
+            .map(|block_id| match self.storage.get_block(block_id) {
+                Ok(op_block) => {
+                    op_block.ok_or_else(|| format_err!("block {:?} should exist", block_id))
+                }
+                Err(e) => bail!("get block {:?} err: {:?}", block_id, e),
+            })
+            .collect::<anyhow::Result<Vec<Block>>>()
+    }
 }
 
 impl ReadableChainService for ChainReaderServiceInner {
diff --git a/commons/forkable-jellyfish-merkle/src/nibble_path/mod.rs b/commons/forkable-jellyfish-merkle/src/nibble_path/mod.rs
index 910fa53cec..15cd2b8ea2 100644
--- a/commons/forkable-jellyfish-merkle/src/nibble_path/mod.rs
+++ b/commons/forkable-jellyfish-merkle/src/nibble_path/mod.rs
@@ -277,14 +277,14 @@ impl<'a> NibbleIterator<'a> {
     }
 
     /// Returns a nibble iterator that iterates all visited nibbles.
-    pub fn visited_nibbles(&self) -> NibbleIterator<'a> {
+    pub fn visited_nibbles(&self) -> Self {
         assume!(self.start <= self.pos.start); // invariant
         assume!(self.pos.start <= ROOT_NIBBLE_HEIGHT); // invariant
         Self::new(self.nibble_path, self.start, self.pos.start)
     }
 
     /// Returns a nibble iterator that iterates all remaining nibbles.
-    pub fn remaining_nibbles(&self) -> NibbleIterator<'a> {
+    pub fn remaining_nibbles(&self) -> Self {
         assume!(self.pos.start <= self.pos.end); // invariant
         assume!(self.pos.end <= ROOT_NIBBLE_HEIGHT); // invariant
         Self::new(self.nibble_path, self.pos.start, self.pos.end)
diff --git a/flexidag/src/blockdag.rs b/flexidag/src/blockdag.rs
index f7164f869b..bb8a50bbb2 100644
--- a/flexidag/src/blockdag.rs
+++ b/flexidag/src/blockdag.rs
@@ -19,10 +19,10 @@ use crate::consensusdb::{
     },
 };
 use crate::ghostdag::protocol::{GhostdagManager, KStore};
-use crate::process_key_already_error;
 use crate::prune::pruning_point_manager::PruningPointManagerT;
 use crate::reachability::reachability_service::ReachabilityService;
 use crate::reachability::ReachabilityError;
+use crate::{process_key_already_error, GetAbsentBlock, GetAbsentBlockResult};
 use anyhow::{bail, ensure, format_err, Ok};
 use itertools::Itertools;
 use parking_lot::Mutex;
@@ -1081,4 +1081,27 @@ impl BlockDAG {
             descendants: de,
         })
     }
+
+    pub fn get_absent_blocks(&self, req: GetAbsentBlock) -> anyhow::Result<GetAbsentBlockResult> {
+        let relation = self.storage.relations_store.read();
+        let mut result = HashSet::from_iter(req.absent_id.clone());
+        let mut roud = req.absent_id.into_iter().collect::<HashSet<HashValue>>();
+        for _ in 0..req.exp {
+            let next_round = roud
+                .into_iter()
+                .map(|parent| relation.get_parents(parent))
+                .collect::<std::result::Result<Vec<_>, _>>()?
+                .into_iter()
+                .flat_map(|v| (*v).clone())
+                .collect::<HashSet<HashValue>>();
+            roud = next_round.difference(&result).cloned().collect();
+            result = result.union(&next_round).cloned().collect();
+        }
+
+        drop(relation);
+
+        Ok(GetAbsentBlockResult {
+            absent_blocks: result.into_iter().collect(),
+        })
+    }
 }
diff --git a/flexidag/src/lib.rs b/flexidag/src/lib.rs
index fd6b356a34..05e8df213b 100644
--- a/flexidag/src/lib.rs
+++ b/flexidag/src/lib.rs
@@ -1,4 +1,5 @@
 use consensusdb::prelude::StoreError;
+use starcoin_crypto::HashValue;
 
 mod block_depth;
 pub mod blockdag;
@@ -15,3 +16,12 @@ pub fn process_key_already_error(result: Result<(), StoreError>) -> Result<(), S
         result
     }
 }
+
+pub struct GetAbsentBlock {
+    pub absent_id: Vec<HashValue>,
+    pub exp: u64,
+}
+
+pub struct GetAbsentBlockResult {
+    pub absent_blocks: Vec<HashValue>,
+}
diff --git a/flexidag/tests/tests.rs b/flexidag/tests/tests.rs
index 7a7c48753b..3479d384ba 100644
--- a/flexidag/tests/tests.rs
+++ b/flexidag/tests/tests.rs
@@ -15,6 +15,7 @@ use starcoin_dag::{
     },
     reachability::{inquirer, ReachabilityError},
     types::{ghostdata::GhostdagData, interval::Interval},
+    GetAbsentBlock,
 };
 use starcoin_logger::prelude::debug;
 use starcoin_types::{
@@ -1563,3 +1564,66 @@ fn test_check_ancestor_of() -> anyhow::Result<()> {
 
     Ok(())
 }
+
+#[test]
+fn test_get_blocks_in_batch() -> anyhow::Result<()> {
+    // initialzie the dag firstly
+    let mut dag = BlockDAG::create_for_testing().unwrap();
+
+    let origin = BlockHeaderBuilder::random().with_number(0).build();
+    let genesis = BlockHeader::dag_genesis_random_with_parent(origin)?;
+
+    dag.init_with_genesis(genesis.clone()).unwrap();
+
+    // one
+    let mut parent = genesis.clone();
+    parent = add_and_print(1, parent.id(), vec![parent.id()], &mut dag)?;
+    let mut number: u64 = 1;
+    let mut pre = vec![parent.id()];
+    let mut next = vec![];
+    for _ in 1..=10 {
+        for parent_id in pre {
+            number = number.saturating_add(1);
+            let one = add_and_print(number, parent_id, vec![parent_id], &mut dag)?;
+            number = number.saturating_add(1);
+            let two = add_and_print(number, parent_id, vec![parent_id], &mut dag)?;
+            next.extend([one.id(), two.id()]);
+        }
+
+        if next.len() >= 8 {
+            number = number.saturating_add(1);
+            let one = add_and_print(number, *next.first().unwrap(), next, &mut dag)?;
+            next = vec![one.id()];
+        }
+        pre = next;
+        next = vec![];
+    }
+
+    number = number.saturating_add(1);
+    let start_point = add_and_print(number, *pre.first().unwrap(), pre, &mut dag)?;
+    let mut pre = vec![start_point.id()];
+    let mut all_results = vec![];
+    for exp in 1..=100u64 {
+        println!("now get absent blocks: {:?}, exp: {:?}", pre.len(), exp);
+        let result = dag.get_absent_blocks(GetAbsentBlock {
+            absent_id: pre,
+            exp,
+        })?;
+        println!(
+            "returned absent blocks: {:?}, exp: {:?}",
+            result.absent_blocks.len(),
+            exp
+        );
+        if result.absent_blocks.is_empty() {
+            break;
+        }
+        all_results.extend(result.absent_blocks.clone());
+        pre = result
+            .absent_blocks
+            .into_iter()
+            .flat_map(|id| (*dag.storage.relations_store.read().get_parents(id).unwrap()).clone())
+            .collect();
+    }
+    assert!(all_results.contains(&genesis.id()));
+    anyhow::Result::Ok(())
+}
diff --git a/kube/manifest/starcoin-halley.yaml b/kube/manifest/starcoin-halley.yaml
index 2322ffaded..c67107df3d 100644
--- a/kube/manifest/starcoin-halley.yaml
+++ b/kube/manifest/starcoin-halley.yaml
@@ -23,7 +23,7 @@ spec:
         starcoin/node-pool: seed-pool
       containers:
       - name: starcoin
-        image: starcoin/starcoin:dag-master
+        image: starcoin/starcoin:batch-pull-absent-blocks
         imagePullPolicy: Always
         command:
           - bash
diff --git a/network-p2p/peerset/src/peersstate.rs b/network-p2p/peerset/src/peersstate.rs
index 3350e1a3ce..355059dac4 100644
--- a/network-p2p/peerset/src/peersstate.rs
+++ b/network-p2p/peerset/src/peersstate.rs
@@ -505,7 +505,7 @@ impl<'a> NotConnectedPeer<'a> {
     /// the slots are full, the node stays "not connected" and we return `Err`.
     ///
     /// Non-slot-occupying nodes don't count towards the number of slots.
-    pub fn try_outgoing(self) -> Result<ConnectedPeer<'a>, NotConnectedPeer<'a>> {
+    pub fn try_outgoing(self) -> Result<ConnectedPeer<'a>, Self> {
         let is_no_slot_occupy = self.state.sets[self.set]
             .no_slot_nodes
             .contains(&*self.peer_id);
@@ -543,7 +543,7 @@ impl<'a> NotConnectedPeer<'a> {
     /// the slots are full, the node stays "not connected" and we return `Err`.
     ///
     /// Non-slot-occupying nodes don't count towards the number of slots.
-    pub fn try_accept_incoming(self) -> Result<ConnectedPeer<'a>, NotConnectedPeer<'a>> {
+    pub fn try_accept_incoming(self) -> Result<ConnectedPeer<'a>, Self> {
         let is_no_slot_occupy = self.state.sets[self.set]
             .no_slot_nodes
             .contains(&*self.peer_id);
diff --git a/network-rpc/api/src/lib.rs b/network-rpc/api/src/lib.rs
index e7c07cc891..d75569d033 100644
--- a/network-rpc/api/src/lib.rs
+++ b/network-rpc/api/src/lib.rs
@@ -17,7 +17,7 @@ use starcoin_state_tree::StateNode;
 use starcoin_types::access_path::AccessPath;
 use starcoin_types::account_address::AccountAddress;
 use starcoin_types::account_state::AccountState;
-use starcoin_types::block::{BlockHeader, BlockInfo, BlockNumber};
+use starcoin_types::block::{Block, BlockHeader, BlockInfo, BlockNumber};
 use starcoin_types::transaction::{SignedUserTransaction, Transaction, TransactionInfo};
 use starcoin_vm_types::state_store::table::TableInfo;
 
@@ -305,6 +305,12 @@ pub trait NetworkRpc: Sized + Send + Sync + 'static {
         peer_id: PeerId,
         req: GetRangeInLocationRequest,
     ) -> BoxFuture<Result<GetRangeInLocationResponse>>;
+
+    fn get_absent_blocks(
+        &self,
+        peer_id: PeerId,
+        req: GetAbsentBlockRequest,
+    ) -> BoxFuture<Result<GetAbsentBlockResponse>>;
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
@@ -332,3 +338,14 @@ pub enum RangeInLocation {
 pub struct GetRangeInLocationResponse {
     pub range: RangeInLocation,
 }
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct GetAbsentBlockRequest {
+    pub absent_id: Vec<HashValue>,
+    pub exp: u64,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct GetAbsentBlockResponse {
+    pub absent_blocks: Vec<Block>,
+}
diff --git a/network-rpc/src/rpc.rs b/network-rpc/src/rpc.rs
index 330cd8bd72..d61db31729 100644
--- a/network-rpc/src/rpc.rs
+++ b/network-rpc/src/rpc.rs
@@ -10,11 +10,11 @@ use starcoin_accumulator::AccumulatorNode;
 use starcoin_chain_service::{ChainAsyncService, ChainReaderService};
 use starcoin_crypto::HashValue;
 use starcoin_network_rpc_api::{
-    gen_server, BlockBody, GetAccountState, GetAccumulatorNodeByNodeHash, GetBlockHeadersByNumber,
-    GetBlockIds, GetRangeInLocationRequest, GetRangeInLocationResponse, GetStateWithProof,
-    GetStateWithTableItemProof, GetTableInfo, GetTxnsWithHash, GetTxnsWithSize, Ping, RpcRequest,
-    MAX_BLOCK_HEADER_REQUEST_SIZE, MAX_BLOCK_INFO_REQUEST_SIZE, MAX_BLOCK_REQUEST_SIZE,
-    MAX_TXN_REQUEST_SIZE,
+    gen_server, BlockBody, GetAbsentBlockRequest, GetAbsentBlockResponse, GetAccountState,
+    GetAccumulatorNodeByNodeHash, GetBlockHeadersByNumber, GetBlockIds, GetRangeInLocationRequest,
+    GetRangeInLocationResponse, GetStateWithProof, GetStateWithTableItemProof, GetTableInfo,
+    GetTxnsWithHash, GetTxnsWithSize, Ping, RpcRequest, MAX_BLOCK_HEADER_REQUEST_SIZE,
+    MAX_BLOCK_INFO_REQUEST_SIZE, MAX_BLOCK_REQUEST_SIZE, MAX_TXN_REQUEST_SIZE,
 };
 use starcoin_service_registry::ServiceRef;
 use starcoin_state_api::{ChainStateAsyncService, StateWithProof, StateWithTableItemProof};
@@ -337,4 +337,14 @@ impl gen_server::NetworkRpc for NetworkRpcImpl {
         let fut = async move { chain_service.get_range_in_location(req).await };
         Box::pin(fut)
     }
+
+    fn get_absent_blocks(
+        &self,
+        _peer_id: PeerId,
+        req: GetAbsentBlockRequest,
+    ) -> BoxFuture<Result<GetAbsentBlockResponse>> {
+        let chain_service = self.chain_service.clone();
+        let fut = async move { chain_service.get_absent_blocks(req).await };
+        Box::pin(fut)
+    }
 }
diff --git a/sync/src/tasks/block_sync_task.rs b/sync/src/tasks/block_sync_task.rs
index 03941e5915..a27f83c844 100644
--- a/sync/src/tasks/block_sync_task.rs
+++ b/sync/src/tasks/block_sync_task.rs
@@ -543,6 +543,82 @@ where
         Ok(result)
     }
 
+    #[allow(dead_code)]
+    async fn fetch_blocks_in_batch(
+        &self,
+        mut block_ids: Vec<HashValue>,
+    ) -> Result<Vec<BlockHeader>> {
+        let mut result = vec![];
+        block_ids.retain(|id| {
+            match self.local_store.get_dag_sync_block(*id) {
+                Ok(op_dag_sync_block) => {
+                    if let Some(dag_sync_block) = op_dag_sync_block {
+                        match self.sync_dag_store.save_block(dag_sync_block.block.clone()) {
+                            Ok(_) => {
+                                result.push(dag_sync_block.block.header().clone());
+                                false // read from local store, remove from p2p request
+                            }
+                            Err(e) => {
+                                debug!("failed to save block for: {:?}", e);
+                                true // need retaining
+                            }
+                        }
+                    } else {
+                        true // need retaining
+                    }
+                }
+                Err(_) => true, // need retaining
+            }
+        });
+
+        let mut exp: u64 = 1;
+        for chunk in block_ids.chunks(usize::try_from(MAX_BLOCK_REQUEST_SIZE)?) {
+            let filtered_chunk = chunk
+                .iter()
+                .filter(|id| {
+                    self.local_store
+                        .get_dag_sync_block(**id)
+                        .unwrap_or(None)
+                        .is_none()
+                        || self.chain.has_dag_block(**id).unwrap_or(true)
+                })
+                .cloned()
+                .collect::<Vec<_>>();
+            if filtered_chunk.is_empty() {
+                continue;
+            }
+            let remote_dag_sync_blocks = self
+                .fetcher
+                .fetch_dag_block_in_batch(filtered_chunk.to_vec(), exp)
+                .await?;
+            for block in remote_dag_sync_blocks {
+                self.local_store
+                    .save_dag_sync_block(starcoin_storage::block::DagSyncBlock {
+                        block: block.clone(),
+                        children: vec![],
+                    })?;
+                self.sync_dag_store.save_block(block.clone())?;
+                result.push(block.header().clone());
+            }
+            if exp < 16 {
+                exp = exp.saturating_mul(2);
+            }
+        }
+
+        Ok(result
+            .into_iter()
+            .filter(|block_header| {
+                !block_header.parents_hash().iter().all(|parent_id| {
+                    self.local_store
+                        .get_dag_sync_block(*parent_id)
+                        .map(|opt_block| opt_block.is_none())
+                        .unwrap_or(true)
+                        || self.chain.has_dag_block(*parent_id).unwrap_or(false)
+                })
+            })
+            .collect())
+    }
+
     pub fn check_enough_by_info(&self, block_info: BlockInfo) -> Result<CollectorState> {
         if block_info.block_accumulator_info.num_leaves
             == self.target.block_info.block_accumulator_info.num_leaves
diff --git a/sync/src/tasks/continue_execute_absent_block.rs b/sync/src/tasks/continue_execute_absent_block.rs
index 18c288af84..7d3eb87dd2 100644
--- a/sync/src/tasks/continue_execute_absent_block.rs
+++ b/sync/src/tasks/continue_execute_absent_block.rs
@@ -27,7 +27,7 @@ impl<'a> ContinueExecuteAbsentBlock<'a> {
         operator: &'a mut dyn ContinueChainOperator,
         local_store: Arc<dyn Store>,
         sync_dag_store: Arc<SyncDagStore>,
-    ) -> anyhow::Result<ContinueExecuteAbsentBlock<'a>> {
+    ) -> anyhow::Result<Self> {
         anyhow::Result::Ok(ContinueExecuteAbsentBlock {
             operator,
             local_store,
diff --git a/sync/src/tasks/mock.rs b/sync/src/tasks/mock.rs
index 08c6a65577..3174a7ef42 100644
--- a/sync/src/tasks/mock.rs
+++ b/sync/src/tasks/mock.rs
@@ -24,8 +24,9 @@ use starcoin_chain_mock::MockChain;
 use starcoin_config::ChainNetwork;
 use starcoin_crypto::HashValue;
 use starcoin_dag::blockdag::BlockDAG;
+use starcoin_dag::GetAbsentBlock;
 use starcoin_network_rpc_api::{RangeInLocation, G_RPC_INFO};
-use starcoin_storage::Storage;
+use starcoin_storage::{BlockStore, Storage};
 use starcoin_sync_api::SyncTarget;
 use starcoin_types::block::{Block, BlockIdAndNumber, BlockInfo, BlockNumber};
 use starcoin_types::startup_info::ChainInfo;
@@ -454,6 +455,35 @@ impl BlockFetcher for SyncNodeMocker {
         }
         .boxed()
     }
+
+    fn fetch_dag_block_in_batch(
+        &self,
+        block_ids: Vec<HashValue>,
+        exp: u64,
+    ) -> BoxFuture<Result<Vec<Block>>> {
+        let dag = self.chain().dag();
+        let result = match dag.get_absent_blocks(GetAbsentBlock {
+            absent_id: block_ids,
+            exp,
+        }) {
+            anyhow::Result::Ok(result) => result
+                .absent_blocks
+                .into_iter()
+                .map(|id| {
+                    self.get_storage()
+                        .get_block(id)?
+                        .ok_or_else(|| format_err!("Block {:?} should exist", id))
+                })
+                .collect::<anyhow::Result<Vec<Block>>>(),
+            Err(e) => anyhow::Result::Err(e),
+        };
+        async move {
+            let _ = self.select_a_peer()?;
+            self.err_mocker.random_err().await?;
+            result
+        }
+        .boxed()
+    }
 }
 
 impl BlockInfoFetcher for SyncNodeMocker {
diff --git a/sync/src/tasks/mod.rs b/sync/src/tasks/mod.rs
index 5ea85fcbbf..46bae9d357 100644
--- a/sync/src/tasks/mod.rs
+++ b/sync/src/tasks/mod.rs
@@ -338,6 +338,12 @@ pub trait BlockFetcher: Send + Sync {
         &self,
         block_ids: Vec<HashValue>,
     ) -> BoxFuture<Result<Vec<HashValue>>>;
+
+    fn fetch_dag_block_in_batch(
+        &self,
+        block_ids: Vec<HashValue>,
+        exp: u64,
+    ) -> BoxFuture<Result<Vec<Block>>>;
 }
 
 impl<T> BlockFetcher for Arc<T>
@@ -364,6 +370,14 @@ where
     ) -> BoxFuture<Result<Vec<HashValue>>> {
         BlockFetcher::fetch_dag_block_children(self.as_ref(), block_ids)
     }
+
+    fn fetch_dag_block_in_batch(
+        &self,
+        block_ids: Vec<HashValue>,
+        exp: u64,
+    ) -> BoxFuture<Result<Vec<Block>>> {
+        BlockFetcher::fetch_dag_block_in_batch(self.as_ref(), block_ids, exp)
+    }
 }
 
 impl BlockFetcher for VerifiedRpcClient {
@@ -404,6 +418,16 @@ impl BlockFetcher for VerifiedRpcClient {
             .map_err(fetcher_err_map)
             .boxed()
     }
+
+    fn fetch_dag_block_in_batch(
+        &self,
+        block_ids: Vec<HashValue>,
+        exp: u64,
+    ) -> BoxFuture<Result<Vec<Block>>> {
+        self.get_absent_blocks(block_ids, exp)
+            .map_err(fetcher_err_map)
+            .boxed()
+    }
 }
 
 pub trait BlockInfoFetcher: Send + Sync {
diff --git a/sync/src/tasks/tests.rs b/sync/src/tasks/tests.rs
index 31477824d7..b6dbb34548 100644
--- a/sync/src/tasks/tests.rs
+++ b/sync/src/tasks/tests.rs
@@ -37,7 +37,7 @@ use starcoin_types::{
     block::{Block, BlockBody, BlockHeaderBuilder, BlockIdAndNumber, BlockInfo},
     U256,
 };
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
 use std::sync::{Arc, Mutex};
 use stream_task::{DefaultCustomErrorHandle, Generator, TaskEventCounterHandle, TaskGenerator};
 use test_helper::DummyNetworkService;
@@ -650,6 +650,36 @@ impl BlockFetcher for MockBlockFetcher {
         }
         .boxed()
     }
+
+    fn fetch_dag_block_in_batch(
+        &self,
+        block_ids: Vec<HashValue>,
+        exp: u64,
+    ) -> BoxFuture<Result<Vec<Block>>> {
+        let mut round = block_ids.into_iter().collect::<HashSet<HashValue>>();
+        let blocks = self.blocks.lock().unwrap();
+        let mut result = HashSet::new();
+        for _ in 0..exp {
+            let find = round
+                .iter()
+                .map(|block_id| {
+                    if let Some(block) = blocks.get(block_id).cloned() {
+                        Ok(block)
+                    } else {
+                        Err(format_err!("Can not find block by id: {:?}", block_id))
+                    }
+                })
+                .collect::<Result<Vec<Block>>>()
+                .unwrap();
+            round = find.iter().map(|block| block.id()).collect();
+            result.extend(find.iter().cloned());
+        }
+        async {
+            Delay::new(Duration::from_millis(100)).await;
+            Ok(result.into_iter().collect())
+        }
+        .boxed()
+    }
 }
 
 fn build_block_fetcher(total_blocks: u64) -> (MockBlockFetcher, MerkleAccumulator) {
diff --git a/sync/src/verified_rpc_client.rs b/sync/src/verified_rpc_client.rs
index 7bce74e3e4..0883a2280a 100644
--- a/sync/src/verified_rpc_client.rs
+++ b/sync/src/verified_rpc_client.rs
@@ -15,7 +15,7 @@ use starcoin_network_rpc_api::{
     gen_client::NetworkRpcClient, BlockBody, GetAccumulatorNodeByNodeHash, GetBlockHeadersByNumber,
     GetBlockIds, GetTxnsWithHash, RawRpcClient,
 };
-use starcoin_network_rpc_api::{GetRangeInLocationRequest, RangeInLocation};
+use starcoin_network_rpc_api::{GetAbsentBlockRequest, GetRangeInLocationRequest, RangeInLocation};
 use starcoin_state_tree::StateNode;
 use starcoin_types::block::Block;
 use starcoin_types::transaction::{SignedUserTransaction, Transaction};
@@ -869,6 +869,38 @@ impl VerifiedRpcClient {
             .collect())
     }
 
+    pub async fn get_absent_blocks(&self, req: Vec<HashValue>, exp: u64) -> Result<Vec<Block>> {
+        let mut count = 0;
+        let peer_id = self.select_a_peer()?;
+        while count < G_RPC_RETRY_COUNT {
+            match self
+                .client
+                .get_absent_blocks(
+                    peer_id.clone(),
+                    GetAbsentBlockRequest {
+                        absent_id: req.clone(),
+                        exp,
+                    },
+                )
+                .await
+            {
+                Ok(result) => return Ok(result.absent_blocks),
+                Err(_) => {
+                    count = count.saturating_add(1);
+                    continue;
+                }
+            }
+        }
+        Err(RpcVerifyError::new(
+            peer_id.clone(),
+            format!(
+                "failed to get dag block children from peer : {:?}.",
+                peer_id
+            ),
+        )
+        .into())
+    }
+
     pub async fn get_dag_block_children(&self, req: Vec<HashValue>) -> Result<Vec<HashValue>> {
         let mut count = 0;
         let peer_id = self.select_a_peer()?;
diff --git a/vm/vm-runtime/src/parallel_executor/storage_wrapper.rs b/vm/vm-runtime/src/parallel_executor/storage_wrapper.rs
index 0a316ac2cc..d0608224fd 100644
--- a/vm/vm-runtime/src/parallel_executor/storage_wrapper.rs
+++ b/vm/vm-runtime/src/parallel_executor/storage_wrapper.rs
@@ -16,7 +16,7 @@ impl<'a, S: StateView> VersionedView<'a, S> {
     pub fn new_view(
         base_view: &'a S,
         hashmap_view: &'a MVHashMapView<'a, StateKey, WriteOp>,
-    ) -> RemoteStorageOwned<VersionedView<'a, S>> {
+    ) -> RemoteStorageOwned<Self> {
         VersionedView {
             base_view,
             hashmap_view,


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>sync/src/tasks/block_sync_task.rs (1)</summary><blockquote>

`546-616`: **Good implementation of batch block fetching with exponential backoff mechanism.**

The `fetch_blocks_in_batch` method efficiently retrieves block headers in batches with an exponential increase in request size. The implementation includes proper filtering of already existing blocks and ensures parent blocks exist before including them in the result.


Consider adding a comment explaining the parent block existence check logic in the final filtering step (lines 604-615), as the condition is quite complex.

</blockquote></details>
<details>
<summary>flexidag/tests/tests.rs (1)</summary><blockquote>

`1568-1629`: **Well-structured test for the batch block fetching functionality.**

This test appropriately verifies the `get_absent_blocks` functionality by building a tree of blocks and then retrieving them in batches with increasing exponential factors. The verification that the genesis block is included in the results is a good basic check.


Consider adding additional test cases for edge conditions such as:
1. Empty input set of blocks
2. Cases where all requested blocks already exist in the chain
3. Testing with different exponential values to verify the batch size behavior

</blockquote></details>
<details>
<summary>sync/src/verified_rpc_client.rs (1)</summary><blockquote>

`872-902`: **Added new method for batch retrieval of absent blocks**

The new `get_absent_blocks` method follows the established pattern of other RPC methods in the class:
1. Uses a retry mechanism with `G_RPC_RETRY_COUNT`
2. Selects a peer and makes the RPC call
3. Returns the result or an appropriate error

One minor issue is in the error message:


The error message mentions "failed to get dag block children" which seems to be a copy-paste error. It should be updated to reflect that this is about absent blocks:

```diff
-                "failed to get dag block children from peer : {:?}.",
+                "failed to get absent blocks from peer : {:?}.",
```

</blockquote></details>
<details>
<summary>sync/src/tasks/mock.rs (1)</summary><blockquote>

`459-486`: **Implementation correctly handles mock DAG block batch fetching**

The implementation correctly:
1. Retrieves blocks from the DAG using `get_absent_blocks`
2. Maps the result to fetch actual blocks from storage
3. Simulates peer interaction and potential network errors

A minor suggestion for the error message at line 475: it currently says "Block {:?} should exist" which assumes the block exists. Consider clarifying the error message to indicate that a block that should exist (according to the DAG) couldn't be found in storage.


```diff
-                        .ok_or_else(|| format_err!("Block {:?} should exist", id))
+                        .ok_or_else(|| format_err!("Block {:?} referenced in DAG not found in storage", id))
```

</blockquote></details>
<details>
<summary>chain/service/src/chain_service.rs (1)</summary><blockquote>

`335-350`: **Implementation correctly retrieves absent blocks from DAG and storage**

The method properly:
1. Fetches absent block IDs from the DAG
2. Retrieves the corresponding blocks from storage
3. Handles errors appropriately

There's a minor inconsistency in error handling style:

```diff
-                    op_block.ok_or_else(|| format_err!("block {:?} should exist", block_id))
+                    op_block.ok_or_else(|| bail!("block {:?} should exist", block_id))
```

Or alternatively, for more consistency with the surrounding code:
```diff
-                Err(e) => bail!("get block {:?} err: {:?}", block_id, e),
+                Err(e) => Err(format_err!("get block {:?} err: {:?}", block_id, e)),
```

</blockquote></details>
<details>
<summary>chain/api/src/service.rs (1)</summary><blockquote>

`510-525`: **Implementation correctly handles absent blocks request and response**

The method properly sends the request and processes the response. However, there's an error message issue:


```diff
-            bail!("get range in location error");
+            bail!("get absent blocks error");
```

This appears to be a copy-paste error from the `get_range_in_location` method.

</blockquote></details>
<details>
<summary>network-rpc/api/src/lib.rs (2)</summary><blockquote>

`342-346`: **Consider implementing RpcRequest for GetAbsentBlockRequest.**

The new request structure lacks validation that's present in other similar request types. Consider implementing the `RpcRequest` trait with a `verify` method to enforce limits on the number of block IDs that can be requested at once.


```diff
+impl RpcRequest for GetAbsentBlockRequest {
+    fn verify(&self) -> Result<()> {
+        if self.absent_id.len() > MAX_BLOCK_REQUEST_SIZE as usize {
+            return Err(NetRpcError::new(
+                RpcErrorCode::BadRequest,
+                format!("too many absent block ids > {}", MAX_BLOCK_REQUEST_SIZE),
+            )
+            .into());
+        }
+        Ok(())
+    }
+}
```

---

`345-345`: **Document the purpose of the exp field.**

The `exp` field's purpose is not immediately clear from its name. Consider renaming it to something more descriptive (e.g., `expiration_time` or `expiration_height`) or adding documentation to clarify its intended use.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6f7ab2286a9bb5bdeb39ec17a41cb91d69da2cfa and 84db9aac7442f46bc804fc786eeb85c833581dce.

</details>

<details>
<summary>📒 Files selected for processing (17)</summary>

* `chain/api/src/message.rs` (2 hunks)
* `chain/api/src/service.rs` (3 hunks)
* `chain/service/src/chain_service.rs` (4 hunks)
* `commons/forkable-jellyfish-merkle/src/nibble_path/mod.rs` (1 hunks)
* `flexidag/src/blockdag.rs` (2 hunks)
* `flexidag/src/lib.rs` (2 hunks)
* `flexidag/tests/tests.rs` (2 hunks)
* `network-p2p/peerset/src/peersstate.rs` (2 hunks)
* `network-rpc/api/src/lib.rs` (3 hunks)
* `network-rpc/src/rpc.rs` (2 hunks)
* `sync/src/tasks/block_sync_task.rs` (1 hunks)
* `sync/src/tasks/continue_execute_absent_block.rs` (1 hunks)
* `sync/src/tasks/mock.rs` (2 hunks)
* `sync/src/tasks/mod.rs` (3 hunks)
* `sync/src/tasks/tests.rs` (2 hunks)
* `sync/src/verified_rpc_client.rs` (2 hunks)
* `vm/vm-runtime/src/parallel_executor/storage_wrapper.rs` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (5)</summary>

<details>
<summary>network-rpc/src/rpc.rs (5)</summary><blockquote>

<details>
<summary>network-rpc/api/src/lib.rs (1)</summary>

* `get_absent_blocks` (309-313)

</details>
<details>
<summary>sync/src/verified_rpc_client.rs (2)</summary>

* `get_absent_blocks` (872-902)
* `req` (69-88)

</details>
<details>
<summary>chain/api/src/service.rs (2)</summary>

* `get_absent_blocks` (170-171)
* `get_absent_blocks` (510-525)

</details>
<details>
<summary>chain/service/src/chain_service.rs (1)</summary>

* `get_absent_blocks` (335-350)

</details>
<details>
<summary>flexidag/src/blockdag.rs (2)</summary>

* `get_absent_blocks` (1085-1106)
* `req` (1088-1088)

</details>

</blockquote></details>
<details>
<summary>chain/service/src/chain_service.rs (4)</summary><blockquote>

<details>
<summary>flexidag/src/blockdag.rs (1)</summary>

* `get_absent_blocks` (1085-1106)

</details>
<details>
<summary>network-rpc/api/src/lib.rs (1)</summary>

* `get_absent_blocks` (309-313)

</details>
<details>
<summary>network-rpc/src/rpc.rs (1)</summary>

* `get_absent_blocks` (341-349)

</details>
<details>
<summary>sync/src/verified_rpc_client.rs (1)</summary>

* `get_absent_blocks` (872-902)

</details>

</blockquote></details>
<details>
<summary>flexidag/tests/tests.rs (3)</summary><blockquote>

<details>
<summary>flexidag/src/blockdag.rs (1)</summary>

* `create_for_testing` (107-114)

</details>
<details>
<summary>flexidag/src/reachability/reachability_service.rs (2)</summary>

* `next` (175-194)
* `next` (223-246)

</details>
<details>
<summary>flexidag/src/types/interval.rs (1)</summary>

* `contains` (175-177)

</details>

</blockquote></details>
<details>
<summary>sync/src/verified_rpc_client.rs (4)</summary><blockquote>

<details>
<summary>network-rpc/src/rpc.rs (1)</summary>

* `get_absent_blocks` (341-349)

</details>
<details>
<summary>network-rpc/api/src/lib.rs (1)</summary>

* `get_absent_blocks` (309-313)

</details>
<details>
<summary>flexidag/src/blockdag.rs (2)</summary>

* `get_absent_blocks` (1085-1106)
* `req` (1088-1088)

</details>
<details>
<summary>chain/service/src/chain_service.rs (2)</summary>

* `get_absent_blocks` (335-350)
* `result` (340-349)

</details>

</blockquote></details>
<details>
<summary>sync/src/tasks/tests.rs (5)</summary><blockquote>

<details>
<summary>sync/src/tasks/mod.rs (6)</summary>

* `fetch_dag_block_in_batch` (342-346)
* `fetch_dag_block_in_batch` (374-380)
* `fetch_dag_block_in_batch` (422-430)
* `block_ids` (390-398)
* `new` (599-601)
* `new` (622-624)

</details>
<details>
<summary>sync/src/tasks/mock.rs (6)</summary>

* `fetch_dag_block_in_batch` (459-486)
* `result` (469-477)
* `new` (54-60)
* `new` (105-107)
* `new` (143-145)
* `new` (190-217)

</details>
<details>
<summary>sync/src/block_connector/write_block_chain.rs (1)</summary>

* `new` (112-146)

</details>
<details>
<summary>sync/src/sync.rs (2)</summary>

* `new` (79-122)
* `new` (815-821)

</details>
<details>
<summary>sync/src/tasks/inner_sync_task.rs (1)</summary>

* `new` (50-76)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Actions: Build and Test</summary>

<details>
<summary>sync/src/tasks/block_sync_task.rs</summary>

[error] 543-543: Cargo formatting check failed. Please run 'cargo fmt' to fix code style issues in this file.

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: run benchmark

</details>

<details>
<summary>🔇 Additional comments (28)</summary><blockquote>

<details>
<summary>flexidag/src/lib.rs (3)</summary>

`2-2`: **Addition of the HashValue import looks good.**

The import of `HashValue` from `starcoin_crypto` is necessary for the new structs being introduced in this file.

---

`20-23`: **New GetAbsentBlock struct looks well-designed.**

This new structure provides a clear contract for requesting absent blocks with:
- `absent_id`: Vector of block hashes to retrieve
- `exp`: Expiration value (likely a timeout or TTL)

The field naming is clear and the types are appropriate.

---

`25-27`: **New GetAbsentBlockResult struct is properly defined.**

This response structure complements the request structure by providing a list of absent block hashes.

</details>
<details>
<summary>chain/api/src/message.rs (2)</summary>

`80-83`: **New GetAbsentBlocks request variant aligns with GetAbsentBlock struct.**

This addition to the ChainRequest enum correctly contains the same fields as the GetAbsentBlock struct:
- `absent_id`: Vector of block hashes to fetch
- `exp`: Expiration value

This maintains consistency across the API.

---

`117-117`: **Response returns block objects rather than just hashes.**

While the `GetAbsentBlockResult` struct used `Vec<HashValue>`, this response variant returns `Vec<Block>`, which provides the complete block data rather than just identifiers. This is sensible as clients typically need the full block data.

</details>
<details>
<summary>commons/forkable-jellyfish-merkle/src/nibble_path/mod.rs (2)</summary>

`280-284`: **Improved return type using Self.**

Using `Self` instead of `NibbleIterator<'a>` is more idiomatic in Rust and makes the code more maintainable. This change doesn't affect functionality since `Self` refers to the implementing type.

---

`287-291`: **Improved return type using Self.**

Using `Self` instead of `NibbleIterator<'a>` is more idiomatic in Rust and makes the code more maintainable. This change doesn't affect functionality since `Self` refers to the implementing type.

</details>
<details>
<summary>sync/src/tasks/continue_execute_absent_block.rs (1)</summary>

`30-30`: **Improved return type using Self.**

Using `Self` instead of `ContinueExecuteAbsentBlock<'a>` is more idiomatic in Rust and makes the code more maintainable. This change doesn't affect functionality since `Self` refers to the implementing type.

</details>
<details>
<summary>network-p2p/peerset/src/peersstate.rs (2)</summary>

`508-508`: **Code readability improvement using Rust's Self type.**

Good change that replaces the explicit return type `NotConnectedPeer<'a>` with `Self`, making the code more maintainable while preserving the same functionality.

---

`546-546`: **Code readability improvement using Rust's Self type.**

Good change that replaces the explicit return type `NotConnectedPeer<'a>` with `Self`, making the code more maintainable while preserving the same functionality.

</details>
<details>
<summary>network-rpc/src/rpc.rs (2)</summary>

`13-17`: **Updated imports to support absent block functionality.**

The imports have been updated to include the new request and response types needed for the absent block retrieval functionality.

---

`341-349`: **Clean implementation of RPC method for retrieving absent blocks.**

The new `get_absent_blocks` method follows the established pattern in this class, delegating the work to the chain service and handling the async flow properly.

</details>
<details>
<summary>vm/vm-runtime/src/parallel_executor/storage_wrapper.rs (1)</summary>

`19-19`: **Good simplification of the return type using `Self`**

This change simplifies the code by using `Self` instead of explicitly writing out the full type `VersionedView<'a, S>`. The functionality remains the same, but the code is now more maintainable and follows Rust's best practices.

</details>
<details>
<summary>sync/src/tasks/tests.rs (2)</summary>

`40-40`: **Added HashSet import for the new implementation**

Adding HashSet to the imports to support the new batch fetching functionality.

---

`654-682`: <details>
<summary>✅ Verification successful</summary>

**Implementation for batch fetching of absent blocks**

The new method implements batch fetching of blocks, which aligns with the PR's objective of handling absent blocks in batches. The implementation processes blocks in iterations based on the expiration parameter, ensuring that blocks are retrieved progressively.

A few observations:
1. Good use of HashSet to efficiently manage unique block IDs
2. The method follows the same pattern as other fetcher methods with the async delay
3. Error handling is in place when blocks can't be found



Let's verify that the expiration parameter is used consistently throughout the codebase:

---

🏁 Script executed:

```shell
#!/bin/bash
# Check how the expiration parameter is used in other parts of the codebase
rg "exp: u64" --context=5
```

Length of output: 6187

---



</details>

**Expiration Parameter Usage Consistency Verified**

The expiration parameter (`exp: u64`) is used consistently throughout the codebase, matching the intended semantics in other modules (e.g., in `sync/src/verified_rpc_client.rs`, `sync/src/tasks/mod.rs`, and `sync/src/tasks/mock.rs`). In `sync/src/tasks/tests.rs`, the use of `exp` in the loop and the associated progressive fetching of blocks aligns with how absent blocks are handled elsewhere. The efficient use of `HashSet` for uniqueness and the clear error handling further strengthen the implementation.

No changes are required based on the expiration parameter usage.

</details>
<details>
<summary>sync/src/verified_rpc_client.rs (1)</summary>

`18-18`: **Add import for GetAbsentBlockRequest**

Added the necessary import to support the new functionality for retrieving absent blocks.

</details>
<details>
<summary>flexidag/src/blockdag.rs (2)</summary>

`25-25`: **Add imports for new types**

Added necessary imports for the `GetAbsentBlock` and `GetAbsentBlockResult` types that are used in the new functionality.

---

`1085-1106`: <details>
<summary>❓ Verification inconclusive</summary>

**Core implementation for retrieving absent blocks in batch**

This method implements the core logic for retrieving absent blocks based on a set of initial block IDs and an expiration value. The algorithm:

1. Builds a result set starting with the initial absent IDs
2. Iteratively retrieves parent blocks up to the expiration depth
3. Efficiently manages unique blocks using HashSet operations
4. Returns all discovered blocks as a result

The implementation is efficient and follows good practices:
- Properly acquires and releases the read lock on relations_store
- Uses `difference` and `union` operations for efficient set operations
- Properly handles errors from relation.get_parents calls



Let's check if there are tests for this new functionality:

---

🏁 Script executed:

```shell
#!/bin/bash
# Look for tests of the get_absent_blocks functionality
rg "test.*get_absent_blocks" --include="*.rs"
```

Length of output: 489

---

**Core Functionality Implementation Approved – Verify Test Coverage**

The `get_absent_blocks` method efficiently handles retrieving absent blocks by:
- Acquiring and releasing the read lock on `relations_store` appropriately.
- Utilizing set operations (`difference` and `union`) for efficient, unique block collection.
- Properly propagating errors from `relation.get_parents`.

Please verify that this functionality is covered by tests. The previous search command encountered argument errors; the revised command above should help locate test cases for `get_absent_blocks`. If tests are missing, kindly add them to ensure robust coverage.



---

🏁 Script executed:

```shell

#!/bin/bash
# Re-run the search for tests of the get_absent_blocks functionality using the correct flag:
rg "test.*get_absent_blocks" -g "*.rs"
```

Length of output: 162

---



</details>

**Test Coverage Needed for `get_absent_blocks` Functionality**

The implementation in `flexidag/src/blockdag.rs` (lines 1085-1106) effectively manages absent block retrieval with proper locking, set operations, and error handling. However, our search commands did not reveal any tests specifically exercising this new functionality. Please verify manually if tests exist under a different naming convention; if not, it is recommended to add tests covering scenarios such as:

- Recursive retrieval of parent blocks up to the specified expiration depth.
- Propagation of errors from `relation.get_parents`.
- Handling of edge cases (e.g., empty input sets, duplicate block IDs).

This verification ensures our codebase maintains robust coverage for this core functionality.

</details>
<details>
<summary>sync/src/tasks/mod.rs (3)</summary>

`342-346`: **Clean trait method addition for batch block fetching**

The new `fetch_dag_block_in_batch` method is a good addition to the `BlockFetcher` trait, providing functionality to fetch DAG blocks in batches with an expiration parameter.

---

`374-380`: **Correct Arc<T> delegation implementation**

The implementation properly delegates to the underlying type's implementation, consistent with other methods in this trait.

---

`422-430`: **Proper VerifiedRpcClient implementation with error handling**

The implementation correctly calls the underlying RPC method and applies consistent error handling through the `fetcher_err_map` function.

</details>
<details>
<summary>sync/src/tasks/mock.rs (1)</summary>

`27-29`: **Appropriate import additions**

The added and modified imports are necessary for the new functionality:
- `GetAbsentBlock` is used as a parameter to the DAG's `get_absent_blocks` method
- `BlockStore` provides the `get_block` method used to retrieve the actual blocks

</details>
<details>
<summary>chain/service/src/chain_service.rs (1)</summary>

`270-274`: **Request handler correctly responds with absent blocks**

The handler properly processes the `GetAbsentBlocks` request and returns the results using the new method in `ChainReaderServiceInner`.

</details>
<details>
<summary>chain/api/src/service.rs (2)</summary>

`10-13`: **Appropriate import additions for the new request/response types**

The updated imports correctly include the necessary types for the new functionality.

---

`170-171`: **Clean trait method addition for absent blocks retrieval**

The method signature is well-defined and consistent with other methods in this trait.

</details>
<details>
<summary>network-rpc/api/src/lib.rs (3)</summary>

`20-20`: **Good import addition.**

The addition of `Block` to the import statement is necessary to support the newly added `GetAbsentBlockResponse` structure, maintaining consistency with the rest of the file.

---

`309-313`: **New method follows established trait pattern.**

The new `get_absent_blocks` method follows the consistent pattern established by other methods in the `NetworkRpc` trait with appropriate parameter types and return type.

---

`347-351`: **Approved response structure.**

The `GetAbsentBlockResponse` structure is well-designed for its purpose, containing a vector of blocks that corresponds to the requested absent block IDs.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:31:49Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>kube/manifest/starcoin-halley.yaml (1)</summary><blockquote>

`26-26`: **Image Tag Update – Verify Commit Hash and Image Policy**

The container image tag has been updated to `starcoin/starcoin:sha-84db9aa`, which ensures that a specific commit is always deployed. Please confirm that the commit hash is correct and reflects the intended state for production. Additionally, consider whether the current `imagePullPolicy: Always` is appropriate given that immutable images (using a specific SHA) might allow for a more efficient policy like `IfNotPresent`.

</blockquote></details>
<details>
<summary>sync/src/tasks/block_sync_task.rs (3)</summary><blockquote>

`546-546`: **Reconsider use of #[allow(dead_code)] attribute**

The `#[allow(dead_code)]` attribute indicates this new method isn't currently being used in the codebase. Consider either:
1. Using this method in the codebase where appropriate
2. Removing the unused method if it's not needed yet
3. Adding a comment explaining why it's kept but unused

---

`576-589`: **Consider improving error handling in filter logic**

The filter logic uses `unwrap_or(true)` to handle potential errors from `has_dag_block()`, which silently converts errors to `true`. This approach hides actual errors that might occur during the operation.


Consider propagating errors properly:

```diff
-                .filter(|id| {
-                    self.local_store
-                        .get_dag_sync_block(**id)
-                        .unwrap_or(None)
-                        .is_none()
-                        || self.chain.has_dag_block(**id).unwrap_or(true)
-                })
+                .filter(|id| -> Result<bool> {
+                    Ok(self.local_store
+                        .get_dag_sync_block(**id)?
+                        .is_none() || self.chain.has_dag_block(**id)?)
+                })
+                .filter_map(|result| match result {
+                    Ok(include) => if include { Some(id.clone()) } else { None },
+                    Err(e) => {
+                        debug!("Error checking block existence: {:?}", e);
+                        None
+                    },
+                })
```

---

`547-620`: **Consider reducing code duplication with existing fetch_blocks method**

The new `fetch_blocks_in_batch` method shares significant logic with the existing `fetch_blocks` method. This creates maintenance overhead as changes to common logic need to be replicated in both places.


Consider refactoring common code into a shared helper method, leaving only the unique batch processing logic in the new method.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 84db9aac7442f46bc804fc786eeb85c833581dce and 1380ceb49cd877cf09fe411ec3285d32cf960653.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `kube/manifest/starcoin-halley.yaml` (1 hunks)
* `sync/src/tasks/block_sync_task.rs` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: build and test

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>sync/src/tasks/block_sync_task.rs (3)</summary>

`574-606`: **Good implementation of exponential backoff mechanism**

The exponential backoff strategy for increasing batch size (starting at 1 and doubling up to 16) is a good approach to optimize network usage while avoiding overloading the network.

---

`590-606`: **New batch fetching mechanism is effective**

The implementation correctly fetches DAG blocks in batch using the new `fetch_dag_block_in_batch` method and saves them to both the local store and sync DAG store, improving efficiency over the previous implementation.

---

`543-543`: **Fix Cargo formatting issue.**

The pipeline has flagged a formatting issue on this line. Please run `cargo fmt` to fix the style.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:38:51Z
user: login: jackzhhuang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T08:15:14Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T08:16:03Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>sync/src/tasks/block_sync_task.rs (2)</summary><blockquote>

`546-620`: **Implementation of batch fetching looks good but consider handling edge cases.**

The new `fetch_blocks_in_batch` method adds valuable functionality for retrieving blocks in batches with exponential backoff, which should improve efficiency. The filter logic at lines 608-617 correctly implements the requirement mentioned in past discussions to include headers where at least one parent exists in the local store but not yet in the chain.

I have a few suggestions:

1. Consider adding documentation explaining the exponential backoff mechanism and why it starts at 1 and doubles up to 16.
2. The method is marked with `#[allow(dead_code)]` but appears to implement important functionality. Make sure this is intentionally unused or remove the attribute when integrating it.
3. There's significant code duplication between this method and `fetch_blocks`. Consider refactoring common logic into a shared method.



```diff
 #[allow(dead_code)]
 async fn fetch_blocks_in_batch(
     &self,
     mut block_ids: Vec<HashValue>,
 ) -> Result<Vec<BlockHeader>> {
+    // Retrieve blocks in batches with exponential backoff for request size (1, 2, 4, 8, 16)
+    // This helps optimize network requests based on server response times
     let mut result = vec![];
     block_ids.retain(|id| {
         match self.local_store.get_dag_sync_block(*id) {
```

---

`574-605`: **Exponential backoff strategy is a good optimization.**

The implementation of exponential backoff (starting at 1 and doubling up to 16) is a good strategy for optimizing network requests. This allows the system to adapt to network conditions by increasing batch sizes when requests succeed quickly.

One suggestion to consider: you might want to add a mechanism to reduce the batch size if a request fails, implementing a true adaptive backoff strategy.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 1380ceb49cd877cf09fe411ec3285d32cf960653 and f5b9ea0fc19ba6fda62c7a5c3d612a63e5dc7de7.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `kube/manifest/starcoin-halley.yaml` (1 hunks)
* `sync/src/tasks/block_sync_task.rs` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* kube/manifest/starcoin-halley.yaml

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>sync/src/tasks/block_sync_task.rs (1)</summary>

```
Learnt from: jackzhhuang
PR: starcoinorg/starcoin#4447
File: sync/src/tasks/block_sync_task.rs:608-620
Timestamp: 2025-04-01T08:16:02.035Z
Learning: In the `fetch_blocks_in_batch` method of `BlockSyncTask`, the filter logic is designed to include block headers where at least one parent exists in the local store but has not yet been integrated into the chain.
```

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: run benchmark

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>sync/src/tasks/block_sync_task.rs (2)</summary>

`611-617`: **Filter logic implementation improved from previous version.**

The complex filter logic correctly implements the requirement to include block headers where at least one parent exists in the local store but hasn't yet been integrated into the chain. This is a crucial part of ensuring proper DAG traversal during synchronization.

The new implementation has improved error handling by using `.map(|opt_block| opt_block.is_none())` instead of `.unwrap_or(None).is_none()` which was used in a previous version mentioned in past reviews.

---

`543-543`: **Fix Cargo formatting issue.**

The pipeline has previously flagged a formatting issue on this line. Please run `cargo fmt` to fix the style.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T08:25:23Z
user: login: sanlee42
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-02T03:50:26Z

Review comments:
diff_hunk: @@ -543,6 +543,78 @@ where
         Ok(result)
path: sync/src/tasks/block_sync_task.rs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix Cargo formatting issue.**

The pipeline has flagged a formatting issue on this line. Please run `cargo fmt` to fix the style.

```shell
#!/bin/bash
# Verify formatting issues
cargo fmt --check
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: Build and Test</summary>

[error] 543-543: Cargo formatting check failed. Please run 'cargo fmt' to fix code style issues in this file.

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-01T07:31:48Z
author_association: NONE
diff_hunk: @@ -543,6 +543,82 @@ where
         Ok(result)
     }
 
+    #[allow(dead_code)]
+    async fn fetch_blocks_in_batch(
+        &self,
+        mut block_ids: Vec<HashValue>,
+    ) -> Result<Vec<BlockHeader>> {
+        let mut result = vec![];
+        block_ids.retain(|id| {
+            match self.local_store.get_dag_sync_block(*id) {
+                Ok(op_dag_sync_block) => {
+                    if let Some(dag_sync_block) = op_dag_sync_block {
+                        match self.sync_dag_store.save_block(dag_sync_block.block.clone()) {
+                            Ok(_) => {
+                                result.push(dag_sync_block.block.header().clone());
+                                false // read from local store, remove from p2p request
+                            }
+                            Err(e) => {
+                                debug!("failed to save block for: {:?}", e);
+                                true // need retaining
+                            }
+                        }
+                    } else {
+                        true // need retaining
+                    }
+                }
+                Err(_) => true, // need retaining
+            }
+        });
+
+        let mut exp: u64 = 1;
+        for chunk in block_ids.chunks(usize::try_from(MAX_BLOCK_REQUEST_SIZE)?) {
+            let filtered_chunk = chunk
+                .iter()
+                .filter(|id| {
+                    self.local_store
+                        .get_dag_sync_block(**id)
+                        .unwrap_or(None)
+                        .is_none()
+                        || self.chain.has_dag_block(**id).unwrap_or(true)
+                })
+                .cloned()
+                .collect::<Vec<_>>();
+            if filtered_chunk.is_empty() {
+                continue;
+            }
+            let remote_dag_sync_blocks = self
+                .fetcher
+                .fetch_dag_block_in_batch(filtered_chunk.to_vec(), exp)
+                .await?;
+            for block in remote_dag_sync_blocks {
+                self.local_store
+                    .save_dag_sync_block(starcoin_storage::block::DagSyncBlock {
+                        block: block.clone(),
+                        children: vec![],
+                    })?;
+                self.sync_dag_store.save_block(block.clone())?;
+                result.push(block.header().clone());
+            }
+            if exp < 16 {
+                exp = exp.saturating_mul(2);
+            }
+        }
+
+        Ok(result
+            .into_iter()
+            .filter(|block_header| {
+                !block_header.parents_hash().iter().all(|parent_id| {
+                    self.local_store
+                        .get_dag_sync_block(*parent_id)
+                        .unwrap_or(None)
+                        .is_none()
+                        || self.chain.has_dag_block(*parent_id).unwrap_or(true)
+                })
+            })
+            .collect())
+    }
path: sync/src/tasks/block_sync_task.rs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Complex filter condition is difficult to reason about**

The filter logic in the return statement uses double negation which makes it difficult to understand:

```rust
!block_header.parents_hash().iter().all(|parent_id| {
    self.local_store
        .get_dag_sync_block(*parent_id)
        .unwrap_or(None)
        .is_none()
        || self.chain.has_dag_block(*parent_id).unwrap_or(true)
})
```

This is filtering headers whose parents are NOT all either (1) non-existent in local store OR (2) existing in chain.


Consider refactoring for clarity and correctness:

```diff
-        Ok(result
-            .into_iter()
-            .filter(|block_header| {
-                !block_header.parents_hash().iter().all(|parent_id| {
-                    self.local_store
-                        .get_dag_sync_block(*parent_id)
-                        .unwrap_or(None)
-                        .is_none()
-                        || self.chain.has_dag_block(*parent_id).unwrap_or(true)
-                })
-            })
-            .collect())
+        Ok(result
+            .into_iter()
+            .filter(|block_header| {
+                // Filter headers where at least one parent exists in either local store or chain
+                block_header.parents_hash().iter().any(|parent_id| {
+                    self.local_store
+                        .get_dag_sync_block(*parent_id)
+                        .map(|opt| opt.is_some())
+                        .unwrap_or(false)
+                    || self.chain.has_dag_block(*parent_id).unwrap_or(false)
+                })
+            })
+            .collect())
```

Also, consider adding a comment explaining the filtering logic clearly.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-01T07:38:51Z
author_association: NONE
diff_hunk: @@ -543,6 +543,82 @@ where
         Ok(result)
     }
 
+    #[allow(dead_code)]
+    async fn fetch_blocks_in_batch(
+        &self,
+        mut block_ids: Vec<HashValue>,
+    ) -> Result<Vec<BlockHeader>> {
+        let mut result = vec![];
+        block_ids.retain(|id| {
+            match self.local_store.get_dag_sync_block(*id) {
+                Ok(op_dag_sync_block) => {
+                    if let Some(dag_sync_block) = op_dag_sync_block {
+                        match self.sync_dag_store.save_block(dag_sync_block.block.clone()) {
+                            Ok(_) => {
+                                result.push(dag_sync_block.block.header().clone());
+                                false // read from local store, remove from p2p request
+                            }
+                            Err(e) => {
+                                debug!("failed to save block for: {:?}", e);
+                                true // need retaining
+                            }
+                        }
+                    } else {
+                        true // need retaining
+                    }
+                }
+                Err(_) => true, // need retaining
+            }
+        });
+
+        let mut exp: u64 = 1;
+        for chunk in block_ids.chunks(usize::try_from(MAX_BLOCK_REQUEST_SIZE)?) {
+            let filtered_chunk = chunk
+                .iter()
+                .filter(|id| {
+                    self.local_store
+                        .get_dag_sync_block(**id)
+                        .unwrap_or(None)
+                        .is_none()
+                        || self.chain.has_dag_block(**id).unwrap_or(true)
+                })
+                .cloned()
+                .collect::<Vec<_>>();
+            if filtered_chunk.is_empty() {
+                continue;
+            }
+            let remote_dag_sync_blocks = self
+                .fetcher
+                .fetch_dag_block_in_batch(filtered_chunk.to_vec(), exp)
+                .await?;
+            for block in remote_dag_sync_blocks {
+                self.local_store
+                    .save_dag_sync_block(starcoin_storage::block::DagSyncBlock {
+                        block: block.clone(),
+                        children: vec![],
+                    })?;
+                self.sync_dag_store.save_block(block.clone())?;
+                result.push(block.header().clone());
+            }
+            if exp < 16 {
+                exp = exp.saturating_mul(2);
+            }
+        }
+
+        Ok(result
+            .into_iter()
+            .filter(|block_header| {
+                !block_header.parents_hash().iter().all(|parent_id| {
+                    self.local_store
+                        .get_dag_sync_block(*parent_id)
+                        .unwrap_or(None)
+                        .is_none()
+                        || self.chain.has_dag_block(*parent_id).unwrap_or(true)
+                })
+            })
+            .collect())
+    }
path: sync/src/tasks/block_sync_task.rs
user: login: jackzhhuang
body: Your comprehension is wrong. The header should be filter only if its parents all are in local store or has_dag_block returns true.
created_at: 2025-04-01T08:15:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -543,6 +543,82 @@ where
         Ok(result)
     }
 
+    #[allow(dead_code)]
+    async fn fetch_blocks_in_batch(
+        &self,
+        mut block_ids: Vec<HashValue>,
+    ) -> Result<Vec<BlockHeader>> {
+        let mut result = vec![];
+        block_ids.retain(|id| {
+            match self.local_store.get_dag_sync_block(*id) {
+                Ok(op_dag_sync_block) => {
+                    if let Some(dag_sync_block) = op_dag_sync_block {
+                        match self.sync_dag_store.save_block(dag_sync_block.block.clone()) {
+                            Ok(_) => {
+                                result.push(dag_sync_block.block.header().clone());
+                                false // read from local store, remove from p2p request
+                            }
+                            Err(e) => {
+                                debug!("failed to save block for: {:?}", e);
+                                true // need retaining
+                            }
+                        }
+                    } else {
+                        true // need retaining
+                    }
+                }
+                Err(_) => true, // need retaining
+            }
+        });
+
+        let mut exp: u64 = 1;
+        for chunk in block_ids.chunks(usize::try_from(MAX_BLOCK_REQUEST_SIZE)?) {
+            let filtered_chunk = chunk
+                .iter()
+                .filter(|id| {
+                    self.local_store
+                        .get_dag_sync_block(**id)
+                        .unwrap_or(None)
+                        .is_none()
+                        || self.chain.has_dag_block(**id).unwrap_or(true)
+                })
+                .cloned()
+                .collect::<Vec<_>>();
+            if filtered_chunk.is_empty() {
+                continue;
+            }
+            let remote_dag_sync_blocks = self
+                .fetcher
+                .fetch_dag_block_in_batch(filtered_chunk.to_vec(), exp)
+                .await?;
+            for block in remote_dag_sync_blocks {
+                self.local_store
+                    .save_dag_sync_block(starcoin_storage::block::DagSyncBlock {
+                        block: block.clone(),
+                        children: vec![],
+                    })?;
+                self.sync_dag_store.save_block(block.clone())?;
+                result.push(block.header().clone());
+            }
+            if exp < 16 {
+                exp = exp.saturating_mul(2);
+            }
+        }
+
+        Ok(result
+            .into_iter()
+            .filter(|block_header| {
+                !block_header.parents_hash().iter().all(|parent_id| {
+                    self.local_store
+                        .get_dag_sync_block(*parent_id)
+                        .unwrap_or(None)
+                        .is_none()
+                        || self.chain.has_dag_block(*parent_id).unwrap_or(true)
+                })
+            })
+            .collect())
+    }
path: sync/src/tasks/block_sync_task.rs
user: login: coderabbitai[bot]
body: @jackzhhuang I apologize for misinterpreting the logic. You're right, and I misunderstood the purpose of the filter.

Looking at it again, the current implementation:
```rust
!block_header.parents_hash().iter().all(|parent_id| {
    self.local_store
        .get_dag_sync_block(*parent_id)
        .unwrap_or(None)
        .is_none()
    || self.chain.has_dag_block(*parent_id).unwrap_or(true)
})
```

This is correctly filtering to include only headers where NOT ALL parents satisfy the condition `(not in local store) OR (in chain)`. In other words, it includes headers where at least one parent fails that condition - meaning at least one parent is both in the local store and not yet in the chain.

This makes sense in the context of batch fetching, as you'd want to process blocks where you have the parent data locally but haven't yet integrated it into the chain.

Thank you for the correction.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: jackzhhuang
PR: starcoinorg/starcoin#4447
File: sync/src/tasks/block_sync_task.rs:608-620
Timestamp: 2025-04-01T08:16:01.886Z
Learning: In the `fetch_blocks_in_batch` method of `BlockSyncTask`, the filter logic is designed to include block headers where at least one parent exists in the local store but has not yet been integrated into the chain.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-01T08:16:03Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-01T07:03:33Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request introduces a new mechanism for handling absent blocks across multiple modules. The changes add new variants to the `ChainRequest` and `ChainResponse` enums, update service traits and their implementations to process absent block requests, extend the DAG logic for fetching absent blocks, and include updates in network RPC and testing components. Additional refinements include simplified return types for iterators and peer connection methods, and updates to batch-fetching logic in sync tasks.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `chain/api/src/message.rs` | Added `GetAbsentBlocks` variants to both `ChainRequest` and `ChainResponse` enums with the required fields. |
| `chain/api/src/service.rs` | Added asynchronous method `get_absent_blocks` to `ChainAsyncService` trait and its implementation, along with necessary imports. |
| `chain/service/src/chain_service.rs` | Introduced handling of `GetAbsentBlocks` in the service handler and added a new method in `ChainReaderServiceInner` for processing absent block requests. |
| `flexidag/src/blockdag.rs` | Added public method `get_absent_blocks` to `BlockDAG` for processing absent block retrieval using rounds based on `exp`. |
| `flexidag/src/lib.rs` | Added new public structures `GetAbsentBlock` and `GetAbsentBlockResult` with appropriate fields and updated imports. |
| `flexidag/tests/tests.rs` | Introduced a new test `test_get_blocks_in_batch` to validate batch processing and absent block retrieval. |
| `network-p2p/peerset/src/peersstate.rs` | Updated the error return types in `try_outgoing` and `try_accept_incoming` methods to use `Self`. |
| `network-rpc/api/src/lib.rs` | Added new method `get_absent_blocks` to the `NetworkRpc` trait and introduced the `GetAbsentBlockRequest` and `GetAbsentBlockResponse` structs. |
| `network-rpc/src/rpc.rs` | Added `get_absent_blocks` method implementation in `NetworkRpcImpl` and updated import statements accordingly. |
| `sync/src/tasks/block_sync_task.rs` | Added `fetch_blocks_in_batch` for batch processing of block headers with an exponential backoff mechanism. |
| `sync/src/tasks/continue_execute_absent_block.rs` | Simplified the return type in the `new` method to use `Self`. |
| `sync/src/tasks/mock.rs` | Introduced `fetch_dag_block_in_batch` to `SyncNodeMocker` with updated storage and request imports. |
| `sync/src/tasks/mod.rs` | Extended the `BlockFetcher` trait with `fetch_dag_block_in_batch` and added implementations for `Arc<T>` and `VerifiedRpcClient`. |
| `sync/src/tasks/tests.rs` | Added the `fetch_dag_block_in_batch` method to `MockBlockFetcher` for testing batch processing of DAG blocks. |
| `sync/src/verified_rpc_client.rs` | Added asynchronous method `get_absent_blocks` with a retry mechanism, and introduced necessary imports for `GetAbsentBlockRequest`. |
| `vm/vm-runtime/src/parallel_executor/storage_wrapper.rs` | Updated the return type in `new_view` to `RemoteStorageOwned<Self>` for conciseness. |
| `kube/manifest/starcoin-halley.yaml` | Updated the image tag for the `starcoin` container to a specific commit SHA. |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant Client
    participant NetworkRPC
    participant ChainAsyncService
    participant ChainService
    participant BlockDAG

    Client->>NetworkRPC: get_absent_blocks(request)
    NetworkRPC->>ChainAsyncService: ChainRequest::GetAbsentBlocks {absent_id, exp}
    ChainAsyncService->>ChainService: Forward request for processing
    ChainService->>BlockDAG: get_absent_blocks(req)
    BlockDAG-->>ChainService: GetAbsentBlockResult
    ChainService-->>ChainAsyncService: ChainResponse::GetAbsentBlocks {absent_blocks}
    ChainAsyncService-->>NetworkRPC: Return response
    NetworkRPC-->>Client: GetAbsentBlockResponse {absent_blocks}
```

## Possibly related PRs
- **starcoinorg/starcoin#4347**: The changes in the main PR, which introduce new request and response types for handling absent blocks, are related to the modifications in the retrieved PR that involve the `sync_dag_store`, as both PRs focus on enhancing the functionality and management of absent blocks within the synchronization process.
- **starcoinorg/starcoin#4369**: The changes in the main PR are related to the introduction of the `GetAbsentBlocks` functionality, which is also referenced in the retrieved PR's modifications to the `BlockDAG` and `NetworkRpc` implementations, indicating a shared focus on handling absent blocks.
- **starcoinorg/starcoin#4199**: The changes in the main PR, which introduce new request and response types for handling absent blocks, are related to the modifications in the retrieved PR that also deal with the management of absent blocks, specifically in how they are processed and added to a waiting list.

## Suggested reviewers
- welbon
- nkysg
- simonjiao
- sanlee42

## Poem
> I hopped through lines of code with glee,  
> Adding blocks that once were lost at sea,  
> Absent blocks found their way to light,  
> Each change a leap, a joyous byte,  
> Let the bugs be chased away—hooray! 🐇  
> In every patch, I dance and play.

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=starcoinorg/starcoin&utm_content=4447):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-01T09:21:40Z
body: ## Benchmark for 9bc2e86
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| accumulator_append | 821.9±137.09µs | 811.6±148.66µs | -1.25% |
| block_apply/block_apply_10 | 511.8±96.80ms | **375.2±15.84ms** | **-26.69%** |
| block_apply/block_apply_1000 | 43.9±1.53s | **43.3±1.85s** | **-1.37%** |
| get_with_proof/db_store | **44.3±3.19µs** | 68.4±16.89µs | **+54.40%** |
| get_with_proof/mem_store | 39.2±7.01µs | **35.7±2.17µs** | **-8.93%** |
| put_and_commit/db_store/1 | **125.4±15.63µs** | 152.3±37.59µs | **+21.45%** |
| put_and_commit/db_store/10 | 1145.7±109.15µs | 1108.9±110.27µs | -3.21% |
| put_and_commit/db_store/100 | 10.6±1.33ms | 11.0±1.43ms | +3.77% |
| put_and_commit/db_store/5 | 672.0±146.76µs | 643.0±115.38µs | -4.32% |
| put_and_commit/db_store/50 | 5.8±1.23ms | 5.4±0.69ms | -6.90% |
| put_and_commit/mem_store/1 | 80.3±15.31µs | 73.6±11.46µs | -8.34% |
| put_and_commit/mem_store/10 | 693.7±115.94µs | 733.5±121.99µs | +5.74% |
| put_and_commit/mem_store/100 | 6.4±0.70ms | 6.6±0.59ms | +3.13% |
| put_and_commit/mem_store/5 | 359.8±49.80µs | 359.7±53.31µs | -0.03% |
| put_and_commit/mem_store/50 | **3.3±0.41ms** | 4.2±1.06ms | **+27.27%** |
| query_block/query_block_in(10)_times(100) | 8.9±0.60ms | **8.3±0.54ms** | **-6.74%** |
| query_block/query_block_in(10)_times(1000) | 119.4±24.49ms | **90.5±15.01ms** | **-24.20%** |
| query_block/query_block_in(10)_times(10000) | 901.4±59.25ms | 871.4±74.67ms | -3.33% |
| query_block/query_block_in(1000)_times(100) | 1370.6±128.96µs | 1292.3±169.18µs | -5.71% |
| query_block/query_block_in(1000)_times(1000) | 12.9±1.08ms | 13.1±0.94ms | +1.55% |
| query_block/query_block_in(1000)_times(10000) | 141.6±17.20ms | **122.9±4.42ms** | **-13.21%** |
| storage_transaction | 1474.6±606.73µs | 1201.3±500.60µs | -18.53% |
| vm/transaction_execution/1 | 407.7±19.67ms | 407.6±14.36ms | -0.02% |
| vm/transaction_execution/10 | **129.1±4.74ms** | 139.5±11.22ms | **+8.06%** |
| vm/transaction_execution/20 | 119.9±6.03ms | 120.1±2.86ms | +0.17% |
| vm/transaction_execution/5 | 164.3±8.90ms | 167.9±16.13ms | +2.19% |
| vm/transaction_execution/50 | 139.5±10.09ms | 144.4±7.79ms | +3.51% |

  </details>
  
author_association: NONE