Skip to content

Commit 9d502a7

Browse files
authored
Send only blocks at missing heights (#5206) (#5218)
Backport of #5206 ## Motivation When syncing chain information for blobs between validators, we were sending more data than necessary. The previous implementation would send **all blocks up to the height** where a blob was published, even when only the specific block containing that blob was needed. For sparse chains (chains with gaps in block heights), this meant sending many unnecessary blocks, wasting network bandwidth and validator processing time. Addresses #5205 (comment) ## Proposal This PR changes the blob synchronization strategy from "send all blocks up to height N" to "send only blocks at specific heights": 1. **Modified `send_chain_info_for_blobs`**: Instead of tracking the maximum height per chain, now collects a `BTreeSet` of all specific heights where blobs exist 2. **New method `send_chain_info_at_heights`**: Sends chain information only for the exact block heights specified, not all blocks leading up to those heights 3. **Updated documentation**: Clarifies that this optimization specifically benefits sparse chains ### Example Impact Previously, if blobs existed at heights [5, 10, 15] on a chain: - Sent blocks 0-5, 0-10, 0-15 (up to 30 blocks sent, with significant duplication) Now: - Sends only blocks at heights 5, 10, 15 (exactly 3 blocks) ## Test Plan - CI ## Release Plan - Nothing to do / These changes follow the usual release cycle. ## Links - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist) Backport of #5206
1 parent caf8df4 commit 9d502a7

1 file changed

Lines changed: 61 additions & 7 deletions

File tree

linera-core/src/updater.rs

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,7 @@ where
648648
/// # Returns
649649
/// - `Ok(())` if synchronization completed successfully or the validator is already up to date
650650
/// - `Err` if there was a communication or storage error
651+
#[instrument(level = "trace", skip_all)]
651652
pub async fn send_chain_information(
652653
&mut self,
653654
chain_id: ChainId,
@@ -882,8 +883,9 @@ where
882883

883884
/// Sends chain information for all chains referenced by the given blobs.
884885
///
885-
/// Reads blob states from storage, determines the chain heights needed,
886-
/// and sends chain information to bring the validator up to date.
886+
/// Reads blob states from storage, determines the specific chain heights needed,
887+
/// and sends chain information for those heights. With sparse chains, this only
888+
/// sends the specific blocks containing the blobs, not all blocks up to those heights.
887889
async fn send_chain_info_for_blobs(
888890
&mut self,
889891
blob_ids: &[BlobId],
@@ -895,20 +897,72 @@ where
895897
.read_blob_states_from_storage(blob_ids)
896898
.await?;
897899

898-
let mut chain_heights = BTreeMap::new();
900+
let mut chain_heights: BTreeMap<ChainId, BTreeSet<BlockHeight>> = BTreeMap::new();
899901
for blob_state in blob_states {
900902
let block_chain_id = blob_state.chain_id;
901-
let block_height = blob_state.block_height.try_add_one()?;
903+
let block_height = blob_state.block_height;
902904
chain_heights
903905
.entry(block_chain_id)
904-
.and_modify(|h| *h = block_height.max(*h))
905-
.or_insert(block_height);
906+
.or_default()
907+
.insert(block_height);
906908
}
907909

908-
self.send_chain_info_up_to_heights(chain_heights, delivery)
910+
self.send_chain_info_at_heights(chain_heights, delivery)
909911
.await
910912
}
911913

914+
/// Sends chain information for specific heights on multiple chains.
915+
///
916+
/// Unlike `send_chain_info_up_to_heights`, this method only sends the blocks at the
917+
/// specified heights, not all blocks up to those heights. This is more efficient for
918+
/// sparse chains where only specific blocks are needed.
919+
async fn send_chain_info_at_heights(
920+
&mut self,
921+
chain_heights: impl IntoIterator<Item = (ChainId, BTreeSet<BlockHeight>)>,
922+
delivery: CrossChainMessageDelivery,
923+
) -> Result<(), chain_client::Error> {
924+
FuturesUnordered::from_iter(chain_heights.into_iter().map(|(chain_id, heights)| {
925+
let mut updater = self.clone();
926+
async move {
927+
// Get all block hashes for this chain at the specified heights in one call
928+
let heights_vec: Vec<_> = heights.into_iter().collect();
929+
let hashes = updater
930+
.client
931+
.local_node
932+
.get_block_hashes(chain_id, heights_vec.clone())
933+
.await?;
934+
935+
if hashes.len() != heights_vec.len() {
936+
return Err(chain_client::Error::InternalError(
937+
"send_chain_info_at_heights called with invalid heights",
938+
));
939+
}
940+
941+
// Read all certificates in one call
942+
let certificates = updater
943+
.client
944+
.local_node
945+
.storage_client()
946+
.read_certificates(hashes.clone())
947+
.await?;
948+
949+
// Send each certificate
950+
for (hash, certificate) in hashes.into_iter().zip(certificates) {
951+
let certificate = certificate
952+
.ok_or_else(|| chain_client::Error::MissingConfirmedBlock(hash))?;
953+
updater
954+
.send_confirmed_certificate(certificate, delivery)
955+
.await?;
956+
}
957+
958+
Ok::<_, chain_client::Error>(())
959+
}
960+
}))
961+
.try_collect::<Vec<_>>()
962+
.await?;
963+
Ok(())
964+
}
965+
912966
async fn send_chain_info_up_to_heights(
913967
&mut self,
914968
chain_heights: impl IntoIterator<Item = (ChainId, BlockHeight)>,

0 commit comments

Comments
 (0)