Skip to content

Commit 4414903

Browse files
authored
Send only blocks at missing heights (#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. - Backport to `main`. ## Links - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
1 parent 0c9c22a commit 4414903

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
@@ -662,6 +662,7 @@ where
662662
/// # Returns
663663
/// - `Ok(())` if synchronization completed successfully or the validator is already up to date
664664
/// - `Err` if there was a communication or storage error
665+
#[instrument(level = "trace", skip_all)]
665666
pub async fn send_chain_information(
666667
&mut self,
667668
chain_id: ChainId,
@@ -896,8 +897,9 @@ where
896897

897898
/// Sends chain information for all chains referenced by the given blobs.
898899
///
899-
/// Reads blob states from storage, determines the chain heights needed,
900-
/// and sends chain information to bring the validator up to date.
900+
/// Reads blob states from storage, determines the specific chain heights needed,
901+
/// and sends chain information for those heights. With sparse chains, this only
902+
/// sends the specific blocks containing the blobs, not all blocks up to those heights.
901903
async fn send_chain_info_for_blobs(
902904
&mut self,
903905
blob_ids: &[BlobId],
@@ -909,20 +911,72 @@ where
909911
.read_blob_states_from_storage(blob_ids)
910912
.await?;
911913

912-
let mut chain_heights = BTreeMap::new();
914+
let mut chain_heights: BTreeMap<ChainId, BTreeSet<BlockHeight>> = BTreeMap::new();
913915
for blob_state in blob_states {
914916
let block_chain_id = blob_state.chain_id;
915-
let block_height = blob_state.block_height.try_add_one()?;
917+
let block_height = blob_state.block_height;
916918
chain_heights
917919
.entry(block_chain_id)
918-
.and_modify(|h| *h = block_height.max(*h))
919-
.or_insert(block_height);
920+
.or_default()
921+
.insert(block_height);
920922
}
921923

922-
self.send_chain_info_up_to_heights(chain_heights, delivery)
924+
self.send_chain_info_at_heights(chain_heights, delivery)
923925
.await
924926
}
925927

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

0 commit comments

Comments
 (0)