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 local_node::LocalNodeClient,
35 node::{CrossChainMessageDelivery, NodeError, ValidatorNode},
36 remote_node::RemoteNode,
37 LocalNodeError,
38};
39
40pub const DEFAULT_QUORUM_GRACE_PERIOD: f64 = 0.2;
43
44pub type ClockSkewReport = (ValidatorPublicKey, TimeDelta);
46const MAX_TIMEOUT: Duration = Duration::from_secs(60 * 60 * 24); #[cfg(with_metrics)]
50pub(crate) mod metrics {
51 use linera_base::prometheus_util::{
52 exponential_bucket_latencies, register_histogram_vec, register_int_counter_vec,
53 };
54 use prometheus::{HistogramVec, IntCounterVec};
55
56 linera_base::declare_metrics! {
57 pub(super) static QUORUM_REQUESTS: IntCounterVec =
67 register_int_counter_vec(
68 "communicate_with_quorum_requests_total",
69 "Requests dispatched to each validator while communicating with a quorum",
70 &["validator", "address"],
71 );
72
73 pub(super) static QUORUM_RESPONSES: IntCounterVec =
81 register_int_counter_vec(
82 "communicate_with_quorum_responses_total",
83 "Responses from each validator, by whether a quorum had already been reached",
84 &["validator", "address", "outcome"],
85 );
86
87 pub(super) static QUORUM_RESPONSE_TIME: HistogramVec =
89 register_histogram_vec(
90 "communicate_with_quorum_response_time_ms",
91 "Time taken by each validator to respond while communicating with a quorum, \
92 in milliseconds",
93 &["validator", "address"],
94 exponential_bucket_latencies(60_000.0),
95 );
96 }
97}
98
99#[derive(Clone)]
101pub enum CommunicateAction {
102 SubmitBlock {
103 proposal: Box<BlockProposal>,
104 blob_ids: Vec<BlobId>,
105 clock_skew_sender: mpsc::UnboundedSender<ClockSkewReport>,
107 },
108 FinalizeBlock {
109 certificate: Box<ValidatedBlockCertificate>,
110 delivery: CrossChainMessageDelivery,
111 },
112 RequestTimeout {
113 chain_id: ChainId,
114 height: BlockHeight,
115 round: Round,
116 },
117}
118
119impl CommunicateAction {
120 pub fn round(&self) -> Round {
122 match self {
123 CommunicateAction::SubmitBlock { proposal, .. } => proposal.content.round,
124 CommunicateAction::FinalizeBlock { certificate, .. } => certificate.round,
125 CommunicateAction::RequestTimeout { round, .. } => *round,
126 }
127 }
128}
129
130pub struct RemoteNodeUpdater<S, N>
137where
138 S: Storage,
139{
140 pub remote_node: RemoteNode<N>,
141 pub local_node: LocalNodeClient<S>,
142 pub admin_chain_id: ChainId,
143 pub certificate_upload_batch_size: usize,
144}
145
146impl<S: Storage + Clone, N: Clone> Clone for RemoteNodeUpdater<S, N> {
147 fn clone(&self) -> Self {
148 RemoteNodeUpdater {
149 remote_node: self.remote_node.clone(),
150 local_node: self.local_node.clone(),
151 admin_chain_id: self.admin_chain_id,
152 certificate_upload_batch_size: self.certificate_upload_batch_size,
153 }
154 }
155}
156
157#[derive(Error, Debug)]
159pub enum CommunicationError<E: fmt::Debug> {
160 #[error(
163 "No error but failed to find a consensus block. Consensus threshold: {0}, Proposals: {1:?}"
164 )]
165 NoConsensus(u64, Vec<(u64, usize)>),
166 #[error("Failed to communicate with a quorum of validators: {0}")]
169 Trusted(E),
170 #[error("Failed to communicate with a quorum of validators:\n{:#?}", .0)]
173 Sample(Vec<(E, u64)>),
174}
175
176pub async fn communicate_with_quorum<'a, A, V, K, F, R, G>(
183 validator_clients: &'a [RemoteNode<A>],
184 committee: &Committee,
185 group_by: G,
186 execute: F,
187 quorum_grace_period: f64,
189) -> Result<(K, Vec<(ValidatorPublicKey, V)>), CommunicationError<NodeError>>
190where
191 A: ValidatorNode + Clone + 'static,
192 F: Clone + Fn(RemoteNode<A>) -> R,
193 R: Future<Output = Result<V, chain_client::Error>> + 'a,
194 G: Fn(&V) -> K,
195 K: Hash + PartialEq + Eq + Clone + 'static,
196 V: 'static,
197{
198 let mut responses: futures::stream::FuturesUnordered<_> = validator_clients
199 .iter()
200 .filter_map(|remote_node| {
201 if committee.weight(&remote_node.public_key) == 0 {
202 return None;
205 }
206 let execute = execute.clone();
207 let remote_node = remote_node.clone();
208 #[cfg(with_metrics)]
209 metrics::QUORUM_REQUESTS
210 .with_label_values(&[&remote_node.public_key.to_string(), &remote_node.address()])
211 .inc();
212 Some(async move {
213 let public_key = remote_node.public_key;
214 #[cfg(with_metrics)]
215 let address = remote_node.address();
216 #[cfg(with_metrics)]
217 let request_start = Instant::now();
218 let result = execute(remote_node).await;
219 #[cfg(with_metrics)]
220 metrics::QUORUM_RESPONSE_TIME
221 .with_label_values(&[&public_key.to_string(), &address])
222 .observe(request_start.elapsed().as_secs_f64() * 1000.0);
223 (public_key, result)
224 })
225 })
226 .collect();
227
228 let start_time = Instant::now();
229 let mut end_time: Option<Instant> = None;
230 let mut remaining_votes = committee.total_votes();
231 let mut highest_key_score = 0;
232 let mut value_scores: HashMap<K, (u64, Vec<(ValidatorPublicKey, V)>)> = HashMap::new();
233 let mut error_scores = HashMap::new();
234 #[cfg(with_metrics)]
235 let addresses: HashMap<ValidatorPublicKey, String> = validator_clients
236 .iter()
237 .map(|remote_node| (remote_node.public_key, remote_node.address()))
238 .collect();
239
240 'vote_wait: while let Ok(Some((name, result))) = timeout(
241 end_time.map_or(MAX_TIMEOUT, |t| t.saturating_duration_since(Instant::now())),
242 responses.next(),
243 )
244 .await
245 {
246 remaining_votes -= committee.weight(&name);
247 #[cfg(with_metrics)]
248 metrics::QUORUM_RESPONSES
249 .with_label_values(&[
250 &name.to_string(),
251 addresses.get(&name).map_or("", String::as_str),
252 if end_time.is_none() {
253 "before_quorum"
254 } else {
255 "after_quorum"
256 },
257 ])
258 .inc();
259 match result {
260 Ok(value) => {
261 let key = group_by(&value);
262 let entry = value_scores.entry(key.clone()).or_insert((0, Vec::new()));
263 entry.0 += committee.weight(&name);
264 entry.1.push((name, value));
265 highest_key_score = highest_key_score.max(entry.0);
266 }
267 Err(err) => {
268 let err = match err {
270 chain_client::Error::RemoteNodeError(err) => err,
271 err => NodeError::ResponseHandlingError {
272 error: err.to_string(),
273 },
274 };
275 let entry = error_scores.entry(err.clone()).or_insert(0);
276 *entry += committee.weight(&name);
277 }
278 }
279 if highest_key_score + remaining_votes < committee.quorum_threshold() {
281 break 'vote_wait;
282 }
283
284 if end_time.is_none() && highest_key_score >= committee.quorum_threshold() {
287 end_time = Some(Instant::now() + start_time.elapsed().mul_f64(quorum_grace_period));
288 }
289 }
290
291 let scores = value_scores
292 .values()
293 .map(|(weight, values)| (*weight, values.len()))
294 .collect();
295 if let Some((key, (_, values))) = value_scores
297 .into_iter()
298 .find(|(_, (score, _))| *score >= committee.quorum_threshold())
299 {
300 return Ok((key, values));
301 }
302
303 let mut sample = error_scores.into_iter().collect::<Vec<_>>();
304 sample.sort_by_key(|(_, score)| std::cmp::Reverse(*score));
305 sample.truncate(4);
306 Err(match sample.as_slice() {
307 [] => CommunicationError::NoConsensus(committee.quorum_threshold(), scores),
308 [(_, score), ..] if *score >= committee.validity_threshold() => {
309 CommunicationError::Trusted(sample.into_iter().next().unwrap().0)
311 }
312 _ => CommunicationError::Sample(sample),
314 })
315}
316
317impl<S, N> RemoteNodeUpdater<S, N>
318where
319 S: Storage + Clone + 'static,
320 N: ValidatorNode + Clone + 'static,
321{
322 fn warn_if_unexpected(&self, err: &NodeError) {
324 if !err.is_expected() {
325 tracing::warn!(
326 remote_node = self.remote_node.address(),
327 %err,
328 "unexpected error from validator",
329 );
330 }
331 }
332
333 #[instrument(
334 level = "trace", skip_all, err(level = Level::DEBUG),
335 fields(chain_id = %certificate.block().header.chain_id)
336 )]
337 async fn send_confirmed_certificate(
339 &mut self,
340 certificate: &CacheArc<ConfirmedBlockCertificate>,
341 delivery: CrossChainMessageDelivery,
342 ) -> Result<Box<ChainInfo>, chain_client::Error> {
343 let mut result = self
344 .remote_node
345 .handle_optimized_confirmed_certificate(certificate, delivery)
346 .await;
347
348 let mut sent_admin_chain = false;
349 let mut sent_blobs = false;
350 let mut sent_blocks = false;
351 loop {
352 match result {
353 Err(NodeError::EventsNotFound(event_ids))
354 if !sent_admin_chain
355 && certificate.inner().chain_id() != self.admin_chain_id
356 && event_ids.iter().all(|event_id| {
357 event_id.stream_id == StreamId::system(EPOCH_STREAM_NAME)
358 && event_id.chain_id == self.admin_chain_id
359 }) =>
360 {
361 self.update_admin_chain().await?;
363 sent_admin_chain = true;
364 }
365 Err(NodeError::BlobsNotFound(blob_ids)) if !sent_blobs => {
366 let cert: &ConfirmedBlockCertificate = certificate;
368 self.remote_node.check_blobs_not_found(cert, &blob_ids)?;
369 let maybe_blobs = self.local_node.read_blobs_from_storage(&blob_ids).await?;
371 let blobs = maybe_blobs.ok_or(NodeError::BlobsNotFound(blob_ids))?;
372 self.remote_node
373 .node
374 .upload_blobs(blobs.into_iter().map(CacheArc::into_std).collect())
375 .await?;
376 sent_blobs = true;
377 }
378 Err(NodeError::BlocksNotFound(hashes)) if !sent_blocks => {
379 let storage = self.local_node.storage_client();
385 let certificates = storage.read_certificates(&hashes).await?;
386 for (hash, maybe_cert) in hashes.iter().zip(certificates) {
387 let cert = maybe_cert.ok_or_else(|| {
388 chain_client::Error::ReadCertificatesError(vec![*hash])
389 })?;
390 self.remote_node
391 .handle_confirmed_certificate(cert, delivery)
392 .await?;
393 }
394 sent_blocks = true;
395 }
396 result => {
397 if let Err(err) = &result {
398 self.warn_if_unexpected(err);
399 }
400 return Ok(result?);
401 }
402 }
403 result = self
404 .remote_node
405 .handle_confirmed_certificate(certificate.clone(), delivery)
406 .await;
407 }
408 }
409
410 async fn send_validated_certificate(
411 &mut self,
412 certificate: ValidatedBlockCertificate,
413 delivery: CrossChainMessageDelivery,
414 ) -> Result<Box<ChainInfo>, chain_client::Error> {
415 let result = self
416 .remote_node
417 .handle_optimized_validated_certificate(&certificate, delivery)
418 .await;
419
420 let chain_id = certificate.inner().chain_id();
421 match &result {
422 Err(original_err @ NodeError::BlobsNotFound(blob_ids)) => {
423 self.remote_node
424 .check_blobs_not_found(&certificate, blob_ids)?;
425 let blobs = self
428 .local_node
429 .get_locking_blobs(blob_ids, chain_id)
430 .await?
431 .ok_or_else(|| original_err.clone())?;
432 self.remote_node.send_pending_blobs(chain_id, blobs).await?;
433 }
434 Err(error) => {
435 self.sync_remote_if_needed(
436 chain_id,
437 certificate.round,
438 certificate.block().header.height,
439 error,
440 )
441 .await?;
442 }
443 _ => return Ok(result?),
444 }
445 let result = self
446 .remote_node
447 .handle_validated_certificate(certificate)
448 .await;
449 if let Err(err) = &result {
450 self.warn_if_unexpected(err);
451 }
452 Ok(result?)
453 }
454
455 async fn request_timeout(
460 &mut self,
461 chain_id: ChainId,
462 round: Round,
463 height: BlockHeight,
464 ) -> Result<Box<ChainInfo>, chain_client::Error> {
465 let query = ChainInfoQuery::new(chain_id).with_timeout(height, round);
466 let result = self
467 .remote_node
468 .handle_chain_info_query(query.clone())
469 .await;
470 if let Err(err) = &result {
471 self.sync_remote_if_needed(chain_id, round, height, err)
472 .await?;
473 self.warn_if_unexpected(err);
474 }
475 Ok(result?)
476 }
477
478 async fn sync_remote_if_needed(
484 &mut self,
485 chain_id: ChainId,
486 round: Round,
487 height: BlockHeight,
488 error: &NodeError,
489 ) -> Result<(), chain_client::Error> {
490 let address = &self.remote_node.address();
491 match error {
492 NodeError::WrongRound(validator_round) if *validator_round > round => {
493 tracing::debug!(
494 address, %chain_id, %validator_round, %round,
495 "validator is at a higher round; local node needs to synchronize",
496 );
497 return Err(chain_client::Error::LocalNodeLagging {
498 chain_id,
499 error: Box::new(error.clone()),
500 });
501 }
502 NodeError::UnexpectedBlockHeight {
503 expected_block_height,
504 found_block_height,
505 } if expected_block_height > found_block_height => {
506 tracing::debug!(
507 address,
508 %chain_id,
509 %expected_block_height,
510 %found_block_height,
511 "validator is at a higher height; local node needs to synchronize",
512 );
513 return Err(chain_client::Error::LocalNodeLagging {
514 chain_id,
515 error: Box::new(error.clone()),
516 });
517 }
518 NodeError::WrongRound(validator_round) if *validator_round < round => {
519 tracing::debug!(
520 address, %chain_id, %validator_round, %round,
521 "validator is at a lower round; sending chain info",
522 );
523 self.send_chain_information(
524 chain_id,
525 height,
526 CrossChainMessageDelivery::NonBlocking,
527 None,
528 )
529 .await?;
530 }
531 NodeError::UnexpectedBlockHeight {
532 expected_block_height,
533 found_block_height,
534 } if expected_block_height < found_block_height => {
535 tracing::debug!(
536 address,
537 %chain_id,
538 %expected_block_height,
539 %found_block_height,
540 "Validator is at a lower height; sending chain info.",
541 );
542 self.send_chain_information(
543 chain_id,
544 height,
545 CrossChainMessageDelivery::NonBlocking,
546 None,
547 )
548 .await?;
549 }
550 NodeError::InactiveChain(inactive_chain_id) => {
551 tracing::debug!(
552 address,
553 chain_id = %inactive_chain_id,
554 "Validator has inactive chain; sending chain info.",
555 );
556 self.send_chain_information(
557 *inactive_chain_id,
558 height,
559 CrossChainMessageDelivery::NonBlocking,
560 None,
561 )
562 .await?;
563 }
564 _ => {}
565 }
566 Ok(())
567 }
568
569 async fn send_block_proposal(
570 &mut self,
571 proposal: Box<BlockProposal>,
572 mut blob_ids: Vec<BlobId>,
573 clock_skew_sender: mpsc::UnboundedSender<ClockSkewReport>,
574 ) -> Result<Box<ChainInfo>, chain_client::Error> {
575 let chain_id = proposal.content.block.chain_id;
576 let mut synced_cross_chain_updates = false;
579 let mut synced_round_and_height = false;
580 let mut publisher_chain_ids_sent = BTreeSet::new();
581 let storage = self.local_node.storage_client();
582 loop {
583 let local_time = storage.clock().current_time();
584 match self
585 .remote_node
586 .handle_block_proposal(proposal.clone())
587 .await
588 {
589 Ok(info) => return Ok(info),
590 Err(err @ (NodeError::WrongRound(_) | NodeError::UnexpectedBlockHeight { .. }))
591 if !synced_round_and_height =>
592 {
593 synced_round_and_height = true;
599 tracing::debug!(
600 remote_node = self.remote_node.address(),
601 %chain_id,
602 %err,
603 "validator disagrees on round or height; synchronizing",
604 );
605 self.sync_remote_if_needed(
606 chain_id,
607 proposal.content.round,
608 proposal.content.block.height,
609 &err,
610 )
611 .await?;
612 }
613 Err(NodeError::MissingCrossChainUpdates {
620 chain_id: dependencies_chain_id,
621 bundles,
622 }) if dependencies_chain_id == proposal.content.block.chain_id => {
623 ensure!(
624 !synced_cross_chain_updates,
625 NodeError::ResponseHandlingError {
626 error: format!(
627 "validator still reports missing cross-chain updates for chain \
628 {dependencies_chain_id} after they were all synced"
629 ),
630 }
631 );
632 synced_cross_chain_updates = true;
633 tracing::debug!(
634 remote_node = %self.remote_node.address(),
635 %chain_id,
636 bundles = bundles.len(),
637 "validator reported missing cross-chain updates; syncing them in one batch",
638 );
639 let mut origin_heights: BTreeMap<ChainId, BlockHeight> = BTreeMap::new();
642 for (origin, height) in bundles {
643 let target = height.try_add_one()?;
644 let entry = origin_heights.entry(origin).or_insert(target);
645 *entry = (*entry).max(target);
646 }
647 self.send_chain_info_up_to_heights(
648 origin_heights,
649 CrossChainMessageDelivery::Blocking,
650 )
651 .await?;
652 }
653 Err(NodeError::EventsNotFound(event_ids)) => {
654 let mut publisher_heights = BTreeMap::new();
655 let chain_ids = event_ids
656 .iter()
657 .map(|event_id| event_id.chain_id)
658 .filter(|chain_id| !publisher_chain_ids_sent.contains(chain_id))
659 .collect::<BTreeSet<_>>();
660 tracing::debug!(
661 remote_node = self.remote_node.address(),
662 ?chain_ids,
663 "missing events; sending chains to validator",
664 );
665 ensure!(!chain_ids.is_empty(), NodeError::EventsNotFound(event_ids));
666 for chain_id in chain_ids {
667 let height = self
668 .local_node
669 .get_next_height_to_preprocess(chain_id)
670 .await?;
671 publisher_heights.insert(chain_id, height);
672 publisher_chain_ids_sent.insert(chain_id);
673 }
674 self.send_chain_info_up_to_heights(
675 publisher_heights,
676 CrossChainMessageDelivery::NonBlocking,
677 )
678 .await?;
679 }
680 Err(error @ NodeError::ChainError { .. }) => {
681 self.warn_if_unexpected(&error);
689 tracing::debug!(
690 remote_node = self.remote_node.address(),
691 %chain_id,
692 %error,
693 "validator rejected proposal; manager state needs to be pulled",
694 );
695 return Err(chain_client::Error::LocalNodeLagging {
696 chain_id,
697 error: Box::new(error),
698 });
699 }
700 Err(NodeError::BlobsNotFound(_) | NodeError::InactiveChain(_))
701 if !blob_ids.is_empty() =>
702 {
703 tracing::debug!("Missing blobs");
704 let published_blob_ids =
708 BTreeSet::from_iter(proposal.content.block.published_blob_ids());
709 blob_ids.retain(|blob_id| !published_blob_ids.contains(blob_id));
710 let published_blobs = self
711 .local_node
712 .get_proposed_blobs(chain_id, published_blob_ids.into_iter().collect())
713 .await?;
714 self.remote_node
715 .send_pending_blobs(chain_id, published_blobs)
716 .await?;
717 let missing_blob_ids = self
718 .remote_node
719 .node
720 .missing_blob_ids(mem::take(&mut blob_ids))
721 .await?;
722
723 tracing::debug!("Sending chains for missing blobs");
724 self.send_chain_info_for_blobs(
725 &missing_blob_ids,
726 CrossChainMessageDelivery::NonBlocking,
727 )
728 .await?;
729 }
730 Err(NodeError::InvalidTimestamp {
731 block_timestamp,
732 local_time: validator_local_time,
733 ..
734 }) => {
735 let clock_skew = local_time.delta_since(validator_local_time);
742 tracing::debug!(
743 remote_node = self.remote_node.address(),
744 %chain_id,
745 %block_timestamp,
746 ?clock_skew,
747 "validator's clock is behind; waiting and retrying",
748 );
749 clock_skew_sender
752 .send((self.remote_node.public_key, clock_skew))
753 .ok();
754 storage
755 .clock()
756 .sleep_until(block_timestamp.saturating_add(clock_skew))
757 .await;
758 }
759 Err(err) => {
761 self.warn_if_unexpected(&err);
762 return Err(err.into());
763 }
764 }
765 }
766 }
767
768 async fn update_admin_chain(&mut self) -> Result<(), chain_client::Error> {
769 let local_admin_info = self.local_node.chain_info(self.admin_chain_id).await?;
770 let admin_chain_id = self.admin_chain_id;
771 let target = local_admin_info.next_block_height;
772 Box::pin(self.send_chain_information(
773 admin_chain_id,
774 target,
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}