1use std::{
6 collections::{BTreeMap, BTreeSet, HashMap},
7 fmt,
8 hash::Hash,
9 mem,
10};
11
12use futures::{future, Future, StreamExt};
13use linera_base::{
14 crypto::ValidatorPublicKey,
15 data_types::{BlockHeight, Round, TimeDelta},
16 ensure,
17 identifiers::{BlobId, BlobType, ChainId, StreamId},
18 time::{timer::timeout, Duration, Instant},
19};
20use linera_chain::{
21 data_types::{BlockProposal, LiteVote},
22 manager::LockingBlock,
23 types::{ConfirmedBlockCertificate, ValidatedBlockCertificate},
24};
25use linera_execution::{committee::Committee, system::EPOCH_STREAM_NAME, BlobOrigin};
26use linera_storage::{Arc as CacheArc, Clock, Storage};
27use thiserror::Error;
28use tokio::sync::mpsc;
29use tracing::{instrument, Level};
30
31use crate::{
32 client::chain_client,
33 data_types::{ChainInfo, ChainInfoQuery},
34 environment::Environment,
35 local_node::LocalNodeClient,
36 node::{CrossChainMessageDelivery, NodeError, ValidatorNode},
37 remote_node::RemoteNode,
38 LocalNodeError,
39};
40
41pub const DEFAULT_QUORUM_GRACE_PERIOD: f64 = 0.2;
44
45pub type ClockSkewReport = (ValidatorPublicKey, TimeDelta);
47const MAX_TIMEOUT: Duration = Duration::from_secs(60 * 60 * 24); #[cfg(with_metrics)]
51mod metrics {
52 use std::sync::LazyLock;
53
54 use linera_base::prometheus_util::{
55 exponential_bucket_latencies, register_histogram_vec, register_int_counter_vec,
56 };
57 use prometheus::{HistogramVec, IntCounterVec};
58
59 pub(super) static QUORUM_REQUESTS: LazyLock<IntCounterVec> = LazyLock::new(|| {
69 register_int_counter_vec(
70 "communicate_with_quorum_requests_total",
71 "Requests dispatched to each validator while communicating with a quorum",
72 &["validator", "address"],
73 )
74 });
75
76 pub(super) static QUORUM_RESPONSES: LazyLock<IntCounterVec> = LazyLock::new(|| {
84 register_int_counter_vec(
85 "communicate_with_quorum_responses_total",
86 "Responses from each validator, by whether a quorum had already been reached",
87 &["validator", "address", "outcome"],
88 )
89 });
90
91 pub(super) static QUORUM_RESPONSE_TIME: LazyLock<HistogramVec> = LazyLock::new(|| {
93 register_histogram_vec(
94 "communicate_with_quorum_response_time_ms",
95 "Time taken by each validator to respond while communicating with a quorum, \
96 in milliseconds",
97 &["validator", "address"],
98 exponential_bucket_latencies(60_000.0),
99 )
100 });
101}
102
103#[derive(Clone)]
105pub enum CommunicateAction {
106 SubmitBlock {
107 proposal: Box<BlockProposal>,
108 blob_ids: Vec<BlobId>,
109 clock_skew_sender: mpsc::UnboundedSender<ClockSkewReport>,
111 },
112 FinalizeBlock {
113 certificate: Box<ValidatedBlockCertificate>,
114 delivery: CrossChainMessageDelivery,
115 },
116 RequestTimeout {
117 chain_id: ChainId,
118 height: BlockHeight,
119 round: Round,
120 },
121}
122
123impl CommunicateAction {
124 pub fn round(&self) -> Round {
126 match self {
127 CommunicateAction::SubmitBlock { proposal, .. } => proposal.content.round,
128 CommunicateAction::FinalizeBlock { certificate, .. } => certificate.round,
129 CommunicateAction::RequestTimeout { round, .. } => *round,
130 }
131 }
132}
133
134pub struct RemoteNodeUpdater<Env>
141where
142 Env: Environment,
143{
144 pub remote_node: RemoteNode<Env::ValidatorNode>,
145 pub local_node: LocalNodeClient<Env::Storage>,
146 pub admin_chain_id: ChainId,
147 pub certificate_upload_batch_size: usize,
148}
149
150impl<Env: Environment> Clone for RemoteNodeUpdater<Env> {
151 fn clone(&self) -> Self {
152 RemoteNodeUpdater {
153 remote_node: self.remote_node.clone(),
154 local_node: self.local_node.clone(),
155 admin_chain_id: self.admin_chain_id,
156 certificate_upload_batch_size: self.certificate_upload_batch_size,
157 }
158 }
159}
160
161#[derive(Error, Debug)]
163pub enum CommunicationError<E: fmt::Debug> {
164 #[error(
167 "No error but failed to find a consensus block. Consensus threshold: {0}, Proposals: {1:?}"
168 )]
169 NoConsensus(u64, Vec<(u64, usize)>),
170 #[error("Failed to communicate with a quorum of validators: {0}")]
173 Trusted(E),
174 #[error("Failed to communicate with a quorum of validators:\n{:#?}", .0)]
177 Sample(Vec<(E, u64)>),
178}
179
180pub async fn communicate_with_quorum<'a, A, V, K, F, R, G>(
187 validator_clients: &'a [RemoteNode<A>],
188 committee: &Committee,
189 group_by: G,
190 execute: F,
191 quorum_grace_period: f64,
193) -> Result<(K, Vec<(ValidatorPublicKey, V)>), CommunicationError<NodeError>>
194where
195 A: ValidatorNode + Clone + 'static,
196 F: Clone + Fn(RemoteNode<A>) -> R,
197 R: Future<Output = Result<V, chain_client::Error>> + 'a,
198 G: Fn(&V) -> K,
199 K: Hash + PartialEq + Eq + Clone + 'static,
200 V: 'static,
201{
202 let mut responses: futures::stream::FuturesUnordered<_> = validator_clients
203 .iter()
204 .filter_map(|remote_node| {
205 if committee.weight(&remote_node.public_key) == 0 {
206 return None;
209 }
210 let execute = execute.clone();
211 let remote_node = remote_node.clone();
212 #[cfg(with_metrics)]
213 metrics::QUORUM_REQUESTS
214 .with_label_values(&[&remote_node.public_key.to_string(), &remote_node.address()])
215 .inc();
216 Some(async move {
217 let public_key = remote_node.public_key;
218 #[cfg(with_metrics)]
219 let address = remote_node.address();
220 #[cfg(with_metrics)]
221 let request_start = Instant::now();
222 let result = execute(remote_node).await;
223 #[cfg(with_metrics)]
224 metrics::QUORUM_RESPONSE_TIME
225 .with_label_values(&[&public_key.to_string(), &address])
226 .observe(request_start.elapsed().as_secs_f64() * 1000.0);
227 (public_key, result)
228 })
229 })
230 .collect();
231
232 let start_time = Instant::now();
233 let mut end_time: Option<Instant> = None;
234 let mut remaining_votes = committee.total_votes();
235 let mut highest_key_score = 0;
236 let mut value_scores: HashMap<K, (u64, Vec<(ValidatorPublicKey, V)>)> = HashMap::new();
237 let mut error_scores = HashMap::new();
238 #[cfg(with_metrics)]
239 let addresses: HashMap<ValidatorPublicKey, String> = validator_clients
240 .iter()
241 .map(|remote_node| (remote_node.public_key, remote_node.address()))
242 .collect();
243
244 'vote_wait: while let Ok(Some((name, result))) = timeout(
245 end_time.map_or(MAX_TIMEOUT, |t| t.saturating_duration_since(Instant::now())),
246 responses.next(),
247 )
248 .await
249 {
250 remaining_votes -= committee.weight(&name);
251 #[cfg(with_metrics)]
252 metrics::QUORUM_RESPONSES
253 .with_label_values(&[
254 &name.to_string(),
255 addresses.get(&name).map_or("", String::as_str),
256 if end_time.is_none() {
257 "before_quorum"
258 } else {
259 "after_quorum"
260 },
261 ])
262 .inc();
263 match result {
264 Ok(value) => {
265 let key = group_by(&value);
266 let entry = value_scores.entry(key.clone()).or_insert((0, Vec::new()));
267 entry.0 += committee.weight(&name);
268 entry.1.push((name, value));
269 highest_key_score = highest_key_score.max(entry.0);
270 }
271 Err(err) => {
272 let err = match err {
274 chain_client::Error::RemoteNodeError(err) => err,
275 err => NodeError::ResponseHandlingError {
276 error: err.to_string(),
277 },
278 };
279 let entry = error_scores.entry(err.clone()).or_insert(0);
280 *entry += committee.weight(&name);
281 }
282 }
283 if highest_key_score + remaining_votes < committee.quorum_threshold() {
285 break 'vote_wait;
286 }
287
288 if end_time.is_none() && highest_key_score >= committee.quorum_threshold() {
291 end_time = Some(Instant::now() + start_time.elapsed().mul_f64(quorum_grace_period));
292 }
293 }
294
295 let scores = value_scores
296 .values()
297 .map(|(weight, values)| (*weight, values.len()))
298 .collect();
299 if let Some((key, (_, values))) = value_scores
301 .into_iter()
302 .find(|(_, (score, _))| *score >= committee.quorum_threshold())
303 {
304 return Ok((key, values));
305 }
306
307 let mut sample = error_scores.into_iter().collect::<Vec<_>>();
308 sample.sort_by_key(|(_, score)| std::cmp::Reverse(*score));
309 sample.truncate(4);
310 Err(match sample.as_slice() {
311 [] => CommunicationError::NoConsensus(committee.quorum_threshold(), scores),
312 [(_, score), ..] if *score >= committee.validity_threshold() => {
313 CommunicationError::Trusted(sample.into_iter().next().unwrap().0)
315 }
316 _ => CommunicationError::Sample(sample),
318 })
319}
320
321impl<Env> RemoteNodeUpdater<Env>
322where
323 Env: Environment + 'static,
324{
325 fn warn_if_unexpected(&self, err: &NodeError) {
327 if !err.is_expected() {
328 tracing::warn!(
329 remote_node = self.remote_node.address(),
330 %err,
331 "unexpected error from validator",
332 );
333 }
334 }
335
336 #[instrument(
337 level = "trace", skip_all, err(level = Level::DEBUG),
338 fields(chain_id = %certificate.block().header.chain_id)
339 )]
340 async fn send_confirmed_certificate(
341 &mut self,
342 certificate: &CacheArc<ConfirmedBlockCertificate>,
343 delivery: CrossChainMessageDelivery,
344 ) -> Result<Box<ChainInfo>, chain_client::Error> {
345 let mut result = self
346 .remote_node
347 .handle_optimized_confirmed_certificate(certificate, delivery)
348 .await;
349
350 let mut sent_admin_chain = false;
351 let mut sent_blobs = false;
352 let mut sent_blocks = false;
353 loop {
354 match result {
355 Err(NodeError::EventsNotFound(event_ids))
356 if !sent_admin_chain
357 && certificate.inner().chain_id() != self.admin_chain_id
358 && event_ids.iter().all(|event_id| {
359 event_id.stream_id == StreamId::system(EPOCH_STREAM_NAME)
360 && event_id.chain_id == self.admin_chain_id
361 }) =>
362 {
363 self.update_admin_chain().await?;
365 sent_admin_chain = true;
366 }
367 Err(NodeError::BlobsNotFound(blob_ids)) if !sent_blobs => {
368 let cert: &ConfirmedBlockCertificate = certificate;
370 self.remote_node.check_blobs_not_found(cert, &blob_ids)?;
371 let maybe_blobs = self.local_node.read_blobs_from_storage(&blob_ids).await?;
373 let blobs = maybe_blobs.ok_or(NodeError::BlobsNotFound(blob_ids))?;
374 self.remote_node
375 .node
376 .upload_blobs(blobs.into_iter().map(|b| b.into_std()).collect())
377 .await?;
378 sent_blobs = true;
379 }
380 Err(NodeError::BlocksNotFound(hashes)) if !sent_blocks => {
381 let storage = self.local_node.storage_client();
387 let certificates = storage.read_certificates(&hashes).await?;
388 for (hash, maybe_cert) in hashes.iter().zip(certificates) {
389 let cert = maybe_cert.ok_or_else(|| {
390 chain_client::Error::ReadCertificatesError(vec![*hash])
391 })?;
392 self.remote_node
393 .handle_confirmed_certificate(cert, delivery)
394 .await?;
395 }
396 sent_blocks = true;
397 }
398 result => {
399 if let Err(err) = &result {
400 self.warn_if_unexpected(err);
401 }
402 return Ok(result?);
403 }
404 }
405 result = self
406 .remote_node
407 .handle_confirmed_certificate(certificate.clone(), delivery)
408 .await;
409 }
410 }
411
412 async fn send_validated_certificate(
413 &mut self,
414 certificate: ValidatedBlockCertificate,
415 delivery: CrossChainMessageDelivery,
416 ) -> Result<Box<ChainInfo>, chain_client::Error> {
417 let result = self
418 .remote_node
419 .handle_optimized_validated_certificate(&certificate, delivery)
420 .await;
421
422 let chain_id = certificate.inner().chain_id();
423 match &result {
424 Err(original_err @ NodeError::BlobsNotFound(blob_ids)) => {
425 self.remote_node
426 .check_blobs_not_found(&certificate, blob_ids)?;
427 let blobs = self
430 .local_node
431 .get_locking_blobs(blob_ids, chain_id)
432 .await?
433 .ok_or_else(|| original_err.clone())?;
434 self.remote_node.send_pending_blobs(chain_id, blobs).await?;
435 }
436 Err(error) => {
437 self.sync_remote_if_needed(
438 chain_id,
439 certificate.round,
440 certificate.block().header.height,
441 error,
442 )
443 .await?;
444 }
445 _ => return Ok(result?),
446 }
447 let result = self
448 .remote_node
449 .handle_validated_certificate(certificate)
450 .await;
451 if let Err(err) = &result {
452 self.warn_if_unexpected(err);
453 }
454 Ok(result?)
455 }
456
457 async fn request_timeout(
462 &mut self,
463 chain_id: ChainId,
464 round: Round,
465 height: BlockHeight,
466 ) -> Result<Box<ChainInfo>, chain_client::Error> {
467 let query = ChainInfoQuery::new(chain_id).with_timeout(height, round);
468 let result = self
469 .remote_node
470 .handle_chain_info_query(query.clone())
471 .await;
472 if let Err(err) = &result {
473 self.sync_remote_if_needed(chain_id, round, height, err)
474 .await?;
475 self.warn_if_unexpected(err);
476 }
477 Ok(result?)
478 }
479
480 async fn sync_remote_if_needed(
486 &mut self,
487 chain_id: ChainId,
488 round: Round,
489 height: BlockHeight,
490 error: &NodeError,
491 ) -> Result<(), chain_client::Error> {
492 let address = &self.remote_node.address();
493 match error {
494 NodeError::WrongRound(validator_round) if *validator_round > round => {
495 tracing::debug!(
496 address, %chain_id, %validator_round, %round,
497 "validator is at a higher round; local node needs to synchronize",
498 );
499 return Err(chain_client::Error::LocalNodeLagging {
500 chain_id,
501 error: Box::new(error.clone()),
502 });
503 }
504 NodeError::UnexpectedBlockHeight {
505 expected_block_height,
506 found_block_height,
507 } if expected_block_height > found_block_height => {
508 tracing::debug!(
509 address,
510 %chain_id,
511 %expected_block_height,
512 %found_block_height,
513 "validator is at a higher height; local node needs to synchronize",
514 );
515 return Err(chain_client::Error::LocalNodeLagging {
516 chain_id,
517 error: Box::new(error.clone()),
518 });
519 }
520 NodeError::WrongRound(validator_round) if *validator_round < round => {
521 tracing::debug!(
522 address, %chain_id, %validator_round, %round,
523 "validator is at a lower round; sending chain info",
524 );
525 self.send_chain_information(
526 chain_id,
527 height,
528 CrossChainMessageDelivery::NonBlocking,
529 None,
530 )
531 .await?;
532 }
533 NodeError::UnexpectedBlockHeight {
534 expected_block_height,
535 found_block_height,
536 } if expected_block_height < found_block_height => {
537 tracing::debug!(
538 address,
539 %chain_id,
540 %expected_block_height,
541 %found_block_height,
542 "Validator is at a lower height; sending chain info.",
543 );
544 self.send_chain_information(
545 chain_id,
546 height,
547 CrossChainMessageDelivery::NonBlocking,
548 None,
549 )
550 .await?;
551 }
552 NodeError::InactiveChain(inactive_chain_id) => {
553 tracing::debug!(
554 address,
555 chain_id = %inactive_chain_id,
556 "Validator has inactive chain; sending chain info.",
557 );
558 self.send_chain_information(
559 *inactive_chain_id,
560 height,
561 CrossChainMessageDelivery::NonBlocking,
562 None,
563 )
564 .await?;
565 }
566 _ => {}
567 }
568 Ok(())
569 }
570
571 async fn send_block_proposal(
572 &mut self,
573 proposal: Box<BlockProposal>,
574 mut blob_ids: Vec<BlobId>,
575 clock_skew_sender: mpsc::UnboundedSender<ClockSkewReport>,
576 ) -> Result<Box<ChainInfo>, chain_client::Error> {
577 let chain_id = proposal.content.block.chain_id;
578 let mut synced_cross_chain_updates = false;
581 let mut synced_round_and_height = false;
582 let mut publisher_chain_ids_sent = BTreeSet::new();
583 let storage = self.local_node.storage_client();
584 loop {
585 let local_time = storage.clock().current_time();
586 match self
587 .remote_node
588 .handle_block_proposal(proposal.clone())
589 .await
590 {
591 Ok(info) => return Ok(info),
592 Err(err @ (NodeError::WrongRound(_) | NodeError::UnexpectedBlockHeight { .. }))
593 if !synced_round_and_height =>
594 {
595 synced_round_and_height = true;
601 tracing::debug!(
602 remote_node = self.remote_node.address(),
603 %chain_id,
604 %err,
605 "validator disagrees on round or height; synchronizing",
606 );
607 self.sync_remote_if_needed(
608 chain_id,
609 proposal.content.round,
610 proposal.content.block.height,
611 &err,
612 )
613 .await?;
614 }
615 Err(NodeError::MissingCrossChainUpdates {
622 chain_id: dependencies_chain_id,
623 bundles,
624 }) if dependencies_chain_id == proposal.content.block.chain_id => {
625 ensure!(
626 !synced_cross_chain_updates,
627 NodeError::ResponseHandlingError {
628 error: format!(
629 "validator still reports missing cross-chain updates for chain \
630 {dependencies_chain_id} after they were all synced"
631 ),
632 }
633 );
634 synced_cross_chain_updates = true;
635 tracing::debug!(
636 remote_node = %self.remote_node.address(),
637 %chain_id,
638 bundles = bundles.len(),
639 "validator reported missing cross-chain updates; syncing them in one batch",
640 );
641 let mut origin_heights: BTreeMap<ChainId, BlockHeight> = BTreeMap::new();
644 for (origin, height) in bundles {
645 let target = height.try_add_one()?;
646 let entry = origin_heights.entry(origin).or_insert(target);
647 *entry = (*entry).max(target);
648 }
649 self.send_chain_info_up_to_heights(
650 origin_heights,
651 CrossChainMessageDelivery::Blocking,
652 )
653 .await?;
654 }
655 Err(NodeError::EventsNotFound(event_ids)) => {
656 let mut publisher_heights = BTreeMap::new();
657 let chain_ids = event_ids
658 .iter()
659 .map(|event_id| event_id.chain_id)
660 .filter(|chain_id| !publisher_chain_ids_sent.contains(chain_id))
661 .collect::<BTreeSet<_>>();
662 tracing::debug!(
663 remote_node = self.remote_node.address(),
664 ?chain_ids,
665 "missing events; sending chains to validator",
666 );
667 ensure!(!chain_ids.is_empty(), NodeError::EventsNotFound(event_ids));
668 for chain_id in chain_ids {
669 let height = self
670 .local_node
671 .get_next_height_to_preprocess(chain_id)
672 .await?;
673 publisher_heights.insert(chain_id, height);
674 publisher_chain_ids_sent.insert(chain_id);
675 }
676 self.send_chain_info_up_to_heights(
677 publisher_heights,
678 CrossChainMessageDelivery::NonBlocking,
679 )
680 .await?;
681 }
682 Err(error @ NodeError::ChainError { .. }) => {
683 self.warn_if_unexpected(&error);
691 tracing::debug!(
692 remote_node = self.remote_node.address(),
693 %chain_id,
694 %error,
695 "validator rejected proposal; manager state needs to be pulled",
696 );
697 return Err(chain_client::Error::LocalNodeLagging {
698 chain_id,
699 error: Box::new(error),
700 });
701 }
702 Err(NodeError::BlobsNotFound(_) | NodeError::InactiveChain(_))
703 if !blob_ids.is_empty() =>
704 {
705 tracing::debug!("Missing blobs");
706 let published_blob_ids =
710 BTreeSet::from_iter(proposal.content.block.published_blob_ids());
711 blob_ids.retain(|blob_id| !published_blob_ids.contains(blob_id));
712 let published_blobs = self
713 .local_node
714 .get_proposed_blobs(chain_id, published_blob_ids.into_iter().collect())
715 .await?;
716 self.remote_node
717 .send_pending_blobs(chain_id, published_blobs)
718 .await?;
719 let missing_blob_ids = self
720 .remote_node
721 .node
722 .missing_blob_ids(mem::take(&mut blob_ids))
723 .await?;
724
725 tracing::debug!("Sending chains for missing blobs");
726 self.send_chain_info_for_blobs(
727 &missing_blob_ids,
728 CrossChainMessageDelivery::NonBlocking,
729 )
730 .await?;
731 }
732 Err(NodeError::InvalidTimestamp {
733 block_timestamp,
734 local_time: validator_local_time,
735 ..
736 }) => {
737 let clock_skew = local_time.delta_since(validator_local_time);
744 tracing::debug!(
745 remote_node = self.remote_node.address(),
746 %chain_id,
747 %block_timestamp,
748 ?clock_skew,
749 "validator's clock is behind; waiting and retrying",
750 );
751 clock_skew_sender
754 .send((self.remote_node.public_key, clock_skew))
755 .ok();
756 storage
757 .clock()
758 .sleep_until(block_timestamp.saturating_add(clock_skew))
759 .await;
760 }
761 Err(err) => {
763 self.warn_if_unexpected(&err);
764 return Err(err.into());
765 }
766 }
767 }
768 }
769
770 async fn update_admin_chain(&mut self) -> Result<(), chain_client::Error> {
771 let local_admin_info = self.local_node.chain_info(self.admin_chain_id).await?;
772 Box::pin(self.send_chain_information(
773 self.admin_chain_id,
774 local_admin_info.next_block_height,
775 CrossChainMessageDelivery::NonBlocking,
776 None,
777 ))
778 .await
779 }
780
781 #[instrument(level = "debug", skip_all, fields(%chain_id))]
827 pub async fn send_chain_information(
828 &mut self,
829 chain_id: ChainId,
830 target_block_height: BlockHeight,
831 delivery: CrossChainMessageDelivery,
832 latest_certificate: Option<CacheArc<ConfirmedBlockCertificate>>,
833 ) -> Result<(), chain_client::Error> {
834 let info = if target_block_height.0 > 0 {
836 self.sync_chain_height(chain_id, target_block_height, delivery, latest_certificate)
837 .await?
838 } else {
839 self.initialize_new_chain_on_validator(chain_id).await?
840 };
841
842 let (remote_height, remote_round) = (info.next_block_height, info.manager.current_round);
846 let query = ChainInfoQuery::new(chain_id).with_manager_values();
847 let local_info = match self.local_node.handle_chain_info_query(query).await {
848 Ok(response) => response.info,
849 Err(LocalNodeError::BlobsNotFound(_)) => {
853 tracing::debug!("local chain description not fully available, skipping round sync");
854 return Ok(());
855 }
856 Err(error) => return Err(error.into()),
857 };
858
859 let manager = local_info.manager;
860 if local_info.next_block_height != remote_height || manager.current_round <= remote_round {
861 return Ok(());
862 }
863
864 self.sync_consensus_round(remote_round, &manager).await
866 }
867
868 async fn sync_chain_height(
878 &mut self,
879 chain_id: ChainId,
880 target_block_height: BlockHeight,
881 delivery: CrossChainMessageDelivery,
882 latest_certificate: Option<CacheArc<ConfirmedBlockCertificate>>,
883 ) -> Result<Box<ChainInfo>, chain_client::Error> {
884 let height = target_block_height.try_sub_one()?;
885
886 let certificate = if let Some(cert) = latest_certificate {
888 cert
889 } else {
890 self.read_certificates_for_heights(chain_id, vec![height])
891 .await?
892 .into_iter()
893 .next()
894 .ok_or_else(|| {
895 chain_client::Error::InternalError(
896 "failed to read latest certificate for height sync",
897 )
898 })?
899 };
900
901 let info = match self
903 .send_confirmed_certificate(&certificate, delivery)
904 .await
905 {
906 Ok(info) => info,
907 Err(error) => {
908 tracing::debug!(
909 address = self.remote_node.address(), %error,
910 "validator failed to handle confirmed certificate; sending whole chain",
911 );
912 let query = ChainInfoQuery::new(chain_id);
913 self.remote_node.handle_chain_info_query(query).await?
914 }
915 };
916
917 let info = self
920 .push_checkpoint_if_useful(chain_id, info, delivery)
921 .await?;
922
923 let heights: Vec<_> = (info.next_block_height.0..target_block_height.0)
925 .map(BlockHeight)
926 .collect();
927
928 if heights.is_empty() {
929 return Ok(info);
930 }
931
932 let batch_size = self.certificate_upload_batch_size;
933 for chunk in heights.chunks(batch_size) {
934 let certificates = self
935 .read_certificates_for_heights(chain_id, chunk.to_vec())
936 .await?;
937
938 for certificate in certificates {
939 self.send_confirmed_certificate(&certificate, delivery)
940 .await?;
941 }
942 }
943
944 Ok(info)
945 }
946
947 async fn read_certificates_for_heights(
954 &self,
955 chain_id: ChainId,
956 heights: Vec<BlockHeight>,
957 ) -> Result<Vec<CacheArc<ConfirmedBlockCertificate>>, chain_client::Error> {
958 let storage = self.local_node.storage_client();
959
960 let certificates_by_height = storage
961 .read_certificates_by_heights(chain_id, &heights)
962 .await?;
963
964 Ok(certificates_by_height.into_iter().flatten().collect())
965 }
966
967 async fn push_checkpoint_if_useful(
975 &mut self,
976 chain_id: ChainId,
977 info: Box<ChainInfo>,
978 delivery: CrossChainMessageDelivery,
979 ) -> Result<Box<ChainInfo>, chain_client::Error> {
980 let local_query = ChainInfoQuery::new(chain_id).with_latest_checkpoint_height();
981 let local_info = self
982 .local_node
983 .handle_chain_info_query(local_query)
984 .await?
985 .info;
986 let Some(checkpoint_height) = local_info.requested_latest_checkpoint_height else {
987 return Ok(info);
988 };
989 if checkpoint_height < info.next_block_height {
990 return Ok(info);
991 }
992 let Some(checkpoint_cert) = self
993 .read_certificates_for_heights(chain_id, vec![checkpoint_height])
994 .await?
995 .into_iter()
996 .next()
997 else {
998 return Ok(info);
999 };
1000 self.send_confirmed_certificate(&checkpoint_cert, delivery)
1001 .await
1002 }
1003
1004 async fn initialize_new_chain_on_validator(
1010 &self,
1011 chain_id: ChainId,
1012 ) -> Result<Box<ChainInfo>, chain_client::Error> {
1013 self.send_chain_info_for_blobs(
1015 &[BlobId::new(chain_id.0, BlobType::ChainDescription)],
1016 CrossChainMessageDelivery::NonBlocking,
1017 )
1018 .await?;
1019
1020 let query = ChainInfoQuery::new(chain_id);
1022 let info = self.remote_node.handle_chain_info_query(query).await?;
1023 Ok(info)
1024 }
1025
1026 async fn sync_consensus_round(
1033 &self,
1034 remote_round: Round,
1035 manager: &linera_chain::manager::ChainManagerInfo,
1036 ) -> Result<(), chain_client::Error> {
1037 let target_round = manager.current_round;
1038
1039 if let Some(LockingBlock::Regular(validated)) = manager.requested_locking.as_deref() {
1044 if validated.round == target_round {
1045 match self
1046 .remote_node
1047 .handle_optimized_validated_certificate(
1048 validated,
1049 CrossChainMessageDelivery::NonBlocking,
1050 )
1051 .await
1052 {
1053 Ok(info) => {
1054 tracing::debug!("successfully sent validated block for round sync");
1055 if info.manager.current_round >= target_round {
1056 return Ok(());
1057 }
1058 }
1059 Err(error) => {
1060 tracing::debug!(%error, "failed to send validated block");
1061 }
1062 }
1063 }
1064 }
1065
1066 if let Some(cert) = &manager.timeout {
1069 if cert.round >= remote_round {
1070 match self
1071 .remote_node
1072 .handle_timeout_certificate(cert.as_ref().clone())
1073 .await
1074 {
1075 Ok(info) => {
1076 tracing::debug!(round = %cert.round, "successfully sent timeout certificate");
1077 if info.manager.current_round >= target_round {
1078 return Ok(());
1079 }
1080 }
1081 Err(error) => {
1082 tracing::debug!(%error, round = %cert.round, "failed to send timeout certificate");
1083 }
1084 }
1085 }
1086 }
1087
1088 for proposal in manager
1090 .requested_proposed
1091 .iter()
1092 .chain(manager.requested_signed_proposal.iter())
1093 {
1094 if proposal.content.round == target_round {
1095 match self
1096 .remote_node
1097 .handle_block_proposal(proposal.clone())
1098 .await
1099 {
1100 Ok(info) => {
1101 tracing::debug!("successfully sent block proposal for round sync");
1102 if info.manager.current_round >= target_round {
1103 return Ok(());
1104 }
1105 }
1106 Err(error) => {
1107 tracing::debug!(%error, "failed to send block proposal");
1108 }
1109 }
1110 }
1111 }
1112
1113 tracing::debug!("round sync not performed: no applicable data or all attempts failed");
1116 Ok(())
1117 }
1118
1119 async fn send_chain_info_for_blobs(
1125 &self,
1126 blob_ids: &[BlobId],
1127 delivery: CrossChainMessageDelivery,
1128 ) -> Result<(), chain_client::Error> {
1129 let blob_states = self
1130 .local_node
1131 .read_blob_states_from_storage(blob_ids)
1132 .await?;
1133
1134 let mut chain_heights: BTreeMap<ChainId, BTreeSet<BlockHeight>> = BTreeMap::new();
1135 for blob_state in blob_states {
1136 match blob_state.origin {
1137 BlobOrigin::Genesis => continue,
1140 BlobOrigin::Published {
1141 chain_id,
1142 block_height,
1143 } => {
1144 chain_heights
1145 .entry(chain_id)
1146 .or_default()
1147 .insert(block_height);
1148 }
1149 }
1150 }
1151
1152 self.send_chain_info_at_heights(chain_heights, delivery)
1153 .await
1154 }
1155
1156 async fn send_chain_info_at_heights(
1163 &self,
1164 chain_heights: impl IntoIterator<Item = (ChainId, BTreeSet<BlockHeight>)>,
1165 delivery: CrossChainMessageDelivery,
1166 ) -> Result<(), chain_client::Error> {
1167 future::try_join_all(chain_heights.into_iter().map(|(chain_id, heights)| {
1168 let mut updater = self.clone();
1169 async move {
1170 let heights_vec = heights.into_iter().collect::<Vec<_>>();
1172 let certificates = updater
1173 .local_node
1174 .storage_client()
1175 .read_certificates_by_heights(chain_id, &heights_vec)
1176 .await?
1177 .into_iter()
1178 .flatten()
1179 .collect::<Vec<_>>();
1180
1181 for certificate in certificates {
1183 updater
1184 .send_confirmed_certificate(&certificate, delivery)
1185 .await?;
1186 }
1187
1188 Ok::<_, chain_client::Error>(())
1189 }
1190 }))
1191 .await?;
1192 Ok(())
1193 }
1194
1195 async fn send_chain_info_up_to_heights(
1198 &self,
1199 chain_heights: impl IntoIterator<Item = (ChainId, BlockHeight)>,
1200 delivery: CrossChainMessageDelivery,
1201 ) -> Result<(), chain_client::Error> {
1202 future::try_join_all(chain_heights.into_iter().map(|(chain_id, height)| {
1203 let mut updater = self.clone();
1204 async move {
1205 updater
1206 .send_chain_information(chain_id, height, delivery, None)
1207 .await
1208 }
1209 }))
1210 .await?;
1211 Ok(())
1212 }
1213
1214 pub async fn send_chain_update(
1215 &mut self,
1216 action: CommunicateAction,
1217 ) -> Result<LiteVote, chain_client::Error> {
1218 let chain_id = match &action {
1219 CommunicateAction::SubmitBlock { proposal, .. } => proposal.content.block.chain_id,
1220 CommunicateAction::FinalizeBlock { certificate, .. } => {
1221 certificate.inner().block().header.chain_id
1222 }
1223 CommunicateAction::RequestTimeout { chain_id, .. } => *chain_id,
1224 };
1225 let vote = match action {
1227 CommunicateAction::SubmitBlock {
1228 proposal,
1229 blob_ids,
1230 clock_skew_sender,
1231 } => {
1232 let info = self
1233 .send_block_proposal(proposal, blob_ids, clock_skew_sender)
1234 .await?;
1235 info.manager.pending.ok_or_else(|| {
1236 NodeError::MissingVoteInValidatorResponse("submit a block proposal".into())
1237 })?
1238 }
1239 CommunicateAction::FinalizeBlock {
1240 certificate,
1241 delivery,
1242 } => {
1243 let info = self
1244 .send_validated_certificate(*certificate, delivery)
1245 .await?;
1246 info.manager.pending.ok_or_else(|| {
1247 NodeError::MissingVoteInValidatorResponse("finalize a block".into())
1248 })?
1249 }
1250 CommunicateAction::RequestTimeout { round, height, .. } => {
1251 let info = self.request_timeout(chain_id, round, height).await?;
1252 info.manager.timeout_vote.ok_or_else(|| {
1253 NodeError::MissingVoteInValidatorResponse("request a timeout".into())
1254 })?
1255 }
1256 };
1257 vote.check(self.remote_node.public_key)?;
1258 Ok(vote)
1259 }
1260}