1mod state;
5use std::{
6 collections::{
7 hash_map::{self, DefaultHasher},
8 BTreeMap, BTreeSet, HashMap, HashSet,
9 },
10 convert::Infallible,
11 hash::{Hash as _, Hasher as _},
12 iter,
13 sync::{
14 atomic::{AtomicU64, Ordering},
15 Arc,
16 },
17};
18
19use custom_debug_derive::Debug;
20use futures::{
21 future::{self, Either, FusedFuture, Future},
22 stream::{self, AbortHandle, FusedStream, FuturesUnordered, StreamExt, TryStreamExt},
23};
24#[cfg(with_metrics)]
25use linera_base::prometheus_util::MeasureLatency as _;
26use linera_base::{
27 abi::Abi,
28 crypto::{signer, CryptoHash, Signer, ValidatorPublicKey},
29 data_types::{
30 Amount, ApplicationDescription, ApplicationPermissions, ArithmeticError, Blob, BlobContent,
31 BlockHeight, ChainDescription, Epoch, MessagePolicy, Round, TimeDelta, Timestamp,
32 },
33 ensure,
34 identifiers::{
35 Account, AccountOwner, ApplicationId, BlobId, BlobType, ChainId, EventId, IndexAndEvent,
36 ModuleId, StreamId,
37 },
38 ownership::{ChainOwnership, TimeoutConfig},
39 time::{Duration, Instant},
40};
41#[cfg(not(target_arch = "wasm32"))]
42use linera_base::{data_types::Bytecode, vm::VmRuntime};
43use linera_chain::{
44 data_types::{
45 BlockProposal, BundleExecutionPolicy, BundleFailurePolicy, ChainAndHeight, IncomingBundle,
46 ProposedBlock, Transaction,
47 },
48 manager::LockingBlock,
49 types::{
50 Block, ConfirmedBlock, ConfirmedBlockCertificate, Timeout, TimeoutCertificate,
51 ValidatedBlock,
52 },
53 ChainError, ChainExecutionContext,
54};
55use linera_execution::{
56 committee::Committee,
57 system::{
58 AdminOperation, OpenChainConfig, SystemOperation, EPOCH_STREAM_NAME,
59 REMOVED_EPOCH_STREAM_NAME,
60 },
61 ExecutionError, Operation, Query, QueryOutcome,
62};
63use linera_storage::{Arc as CacheArc, Clock as _, Storage as _};
64use linera_views::ViewError;
65use serde::Serialize;
66pub use state::State;
67use thiserror::Error;
68use tokio::sync::mpsc;
69use tokio_stream::wrappers::UnboundedReceiverStream;
70use tracing::{debug, error, info, instrument, trace, warn, Instrument as _};
71
72use super::{
73 received_log::ReceivedLogs, validator_trackers::ValidatorTrackers, AbortOnDrop, Client,
74 ListeningMode, PendingProposal, TimingType,
75};
76use crate::{
77 data_types::{ChainInfo, ChainInfoQuery, ClientOutcome, RoundTimeout},
78 environment::Environment,
79 local_node::{LocalNodeClient, LocalNodeError},
80 node::{
81 CrossChainMessageDelivery, NodeError, NotificationStream, ValidatorNode,
82 ValidatorNodeProvider as _,
83 },
84 remote_node::RemoteNode,
85 updater::{communicate_with_quorum, CommunicateAction, CommunicationError},
86 worker::{Notification, Reason, WorkerError},
87 MaybeSendBoxFuture,
88};
89
90const LARGE_RECEIVED_CERTIFICATE_SYNC_THRESHOLD: usize = 50_000;
95
96const RECEIVED_CERTIFICATE_SYNC_PROGRESS_INTERVAL: usize = 100_000;
99
100#[derive(Debug, Clone)]
102pub struct Options {
103 pub max_pending_message_bundles: usize,
105 pub max_block_limit_errors: u32,
110 pub staging_bundles_time_budget: Option<Duration>,
113 pub message_policy: MessagePolicy,
115 pub priority_bundle_origins: HashSet<ChainId>,
117 pub cross_chain_message_delivery: CrossChainMessageDelivery,
119 pub quorum_grace_period: f64,
122 pub blob_download_hedge_delay: Duration,
124 pub certificate_batch_download_hedge_delay: Duration,
126 pub certificate_download_batch_size: u64,
129 pub certificate_upload_batch_size: usize,
132 pub sender_certificate_download_batch_size: usize,
135 pub max_concurrent_batch_downloads: usize,
137 pub max_joined_tasks: usize,
139 pub allow_fast_blocks: bool,
142 pub notification_circuit_breaker_initial_probe_interval: Duration,
146 pub notification_circuit_breaker_max_probe_interval: Duration,
149 pub max_event_stream_queries: usize,
152}
153
154pub(super) struct StreamHandle {
156 pub(super) abort: AbortHandle,
157 pub(super) serving_since: Arc<AtomicU64>,
161}
162
163impl StreamHandle {
164 fn serving_for(&self, now: Timestamp) -> Option<Duration> {
166 match self.serving_since.load(Ordering::Relaxed) {
167 NOT_SERVING => None,
168 micros => Some(now.duration_since(Timestamp::from(micros))),
169 }
170 }
171}
172
173pub(super) struct CircuitBreakerState {
174 pub(super) next_probe_at: Timestamp,
175 pub(super) probe_interval: Duration,
176 pub(super) tripped: bool,
179}
180
181impl CircuitBreakerState {
182 fn launched(now: Timestamp, probe_interval: Duration, spread: Duration) -> Self {
184 Self::armed(now, probe_interval, spread, false)
185 }
186
187 fn failed(now: Timestamp, probe_interval: Duration, spread: Duration) -> Self {
189 Self::armed(now, probe_interval, spread, true)
190 }
191
192 fn churned(now: Timestamp, probe_interval: Duration, spread: Duration) -> Self {
198 Self::armed(now, probe_interval, spread, false)
199 }
200
201 fn armed(now: Timestamp, probe_interval: Duration, spread: Duration, tripped: bool) -> Self {
202 CircuitBreakerState {
203 next_probe_at: now.saturating_add(TimeDelta::from_duration(probe_interval + spread)),
204 probe_interval,
205 tripped,
206 }
207 }
208
209 fn escalate(&mut self, now: Timestamp, max: Duration, spread: Duration) {
212 self.probe_interval = self.probe_interval.saturating_mul(2).min(max);
213 self.tripped = true;
214 self.arm(now, spread);
215 }
216
217 fn arm(&mut self, now: Timestamp, spread: Duration) {
221 self.next_probe_at =
222 now.saturating_add(TimeDelta::from_duration(self.probe_interval + spread));
223 }
224}
225
226pub(super) const NOT_SERVING: u64 = u64::MAX;
228
229const MIN_PROBE_INTERVAL: Duration = Duration::from_secs(1);
235
236const PROBE_SPREAD_DIVISOR: u32 = 8;
238
239const FIRST_LAUNCH_GRACE: u32 = 4;
247
248#[derive(Clone, Copy, Debug, PartialEq, Eq)]
251struct ConsensusStateSnapshot {
252 next_block_height: BlockHeight,
253 current_round: Round,
254 lock_round: Option<Round>,
255 timeout_round: Option<Round>,
256}
257
258#[cfg(with_testing)]
259impl Options {
260 pub fn test_default() -> Self {
262 use super::{
263 DEFAULT_CERTIFICATE_DOWNLOAD_BATCH_SIZE, DEFAULT_CERTIFICATE_UPLOAD_BATCH_SIZE,
264 DEFAULT_MAX_CONCURRENT_BATCH_DOWNLOADS, DEFAULT_MAX_EVENT_STREAM_QUERIES,
265 DEFAULT_SENDER_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
266 };
267 use crate::DEFAULT_QUORUM_GRACE_PERIOD;
268
269 Options {
270 max_pending_message_bundles: 10,
271 max_block_limit_errors: 3,
272 staging_bundles_time_budget: None,
273 message_policy: MessagePolicy::default(),
274 priority_bundle_origins: HashSet::new(),
275 cross_chain_message_delivery: CrossChainMessageDelivery::NonBlocking,
276 quorum_grace_period: DEFAULT_QUORUM_GRACE_PERIOD,
277 blob_download_hedge_delay: Duration::from_secs(1),
278 certificate_batch_download_hedge_delay: Duration::from_secs(1),
279 certificate_download_batch_size: DEFAULT_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
280 certificate_upload_batch_size: DEFAULT_CERTIFICATE_UPLOAD_BATCH_SIZE,
281 sender_certificate_download_batch_size: DEFAULT_SENDER_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
282 max_concurrent_batch_downloads: DEFAULT_MAX_CONCURRENT_BATCH_DOWNLOADS,
283 max_joined_tasks: 100,
284 allow_fast_blocks: false,
285 notification_circuit_breaker_initial_probe_interval: Duration::from_secs(300),
286 notification_circuit_breaker_max_probe_interval: Duration::from_secs(3600),
287 max_event_stream_queries: DEFAULT_MAX_EVENT_STREAM_QUERIES,
288 }
289 }
290}
291
292impl Options {
293 pub fn bundle_execution_policy(&self) -> BundleExecutionPolicy {
295 BundleExecutionPolicy {
296 on_failure: BundleFailurePolicy::AutoRetry {
297 max_failures: self.max_block_limit_errors,
298 never_reject_application_ids: Arc::new(
299 self.message_policy.never_reject_application_ids.clone(),
300 ),
301 },
302 time_budget: self.staging_bundles_time_budget,
303 }
304 }
305}
306
307#[derive(Debug)]
313pub struct ChainClient<Env: Environment> {
314 #[debug(skip)]
316 pub(crate) client: Arc<Client<Env>>,
317 chain_id: ChainId,
319 #[debug(skip)]
321 options: Options,
322 preferred_owner: Option<AccountOwner>,
325 initial_next_block_height: BlockHeight,
327 initial_block_hash: Option<CryptoHash>,
329 timing_sender: Option<mpsc::UnboundedSender<(u64, TimingType)>>,
331 skipped_origins: Arc<papaya::HashSet<ChainId>>,
334}
335
336impl<Env: Environment> Clone for ChainClient<Env> {
337 fn clone(&self) -> Self {
338 Self {
339 client: self.client.clone(),
340 chain_id: self.chain_id,
341 options: self.options.clone(),
342 preferred_owner: self.preferred_owner,
343 initial_next_block_height: self.initial_next_block_height,
344 initial_block_hash: self.initial_block_hash,
345 timing_sender: self.timing_sender.clone(),
346 skipped_origins: self.skipped_origins.clone(),
347 }
348 }
349}
350
351#[derive(Debug, Error, strum::IntoStaticStr)]
353#[allow(missing_docs)]
354pub enum Error {
355 #[error("Local node operation failed: {0}")]
356 LocalNodeError(#[from] LocalNodeError),
357
358 #[error("Remote node operation failed: {0}")]
359 RemoteNodeError(#[from] NodeError),
360
361 #[error("The local node is lagging behind a validator on chain {chain_id}: {error}")]
364 LocalNodeLagging {
365 chain_id: ChainId,
366 error: Box<NodeError>,
367 },
368
369 #[error(transparent)]
370 ArithmeticError(#[from] ArithmeticError),
371
372 #[error("Missing certificates: {0:?}")]
373 ReadCertificatesError(Vec<CryptoHash>),
374
375 #[error("Missing confirmed block: {0:?}")]
376 MissingConfirmedBlock(CryptoHash),
377
378 #[error("JSON (de)serialization error: {0}")]
379 JsonError(#[from] serde_json::Error),
380
381 #[error("Chain operation failed: {0}")]
382 ChainError(#[from] ChainError),
383
384 #[error(transparent)]
385 CommunicationError(#[from] CommunicationError<NodeError>),
386
387 #[error("Internal error within chain client: {0}")]
388 InternalError(&'static str),
389
390 #[error(
391 "Cannot accept a certificate from an unknown committee in the future. \
392 Please synchronize the local view of the admin chain"
393 )]
394 CommitteeSynchronizationError,
395
396 #[error("The local node is behind the trusted state in wallet and needs synchronization with validators")]
397 WalletSynchronizationError,
398
399 #[error("The state of the client is incompatible with the proposed block: {0}")]
400 BlockProposalError(&'static str),
401
402 #[error(
403 "Cannot accept a certificate from a committee that was retired. \
404 Try a newer certificate from the same origin"
405 )]
406 CommitteeDeprecationError,
407
408 #[error("Protocol error within chain client: {0}")]
409 ProtocolError(&'static str),
410
411 #[error("Signer doesn't have key to sign for chain {0}")]
412 CannotFindKeyForChain(ChainId),
413
414 #[error("client is not configured to propose on chain {0}")]
415 NoAccountKeyConfigured(ChainId),
416
417 #[error("The chain client isn't owner on chain {0}")]
418 NotAnOwner(ChainId),
419
420 #[error(transparent)]
421 ViewError(#[from] ViewError),
422
423 #[error(
424 "Failed to download certificates and update local node to the next height \
425 {target_next_block_height} of chain {chain_id}"
426 )]
427 CannotDownloadCertificates {
428 chain_id: ChainId,
429 target_next_block_height: BlockHeight,
430 },
431
432 #[error("No validator provided a usable certificate registering blob {0}")]
433 CannotDownloadBlob(BlobId),
434
435 #[error(transparent)]
436 BcsError(#[from] bcs::Error),
437
438 #[error(
439 "Unexpected quorum: validators voted for block hash {hash} in {round}, \
440 expected block hash {expected_hash} in {expected_round}"
441 )]
442 UnexpectedQuorum {
443 hash: CryptoHash,
444 round: Round,
445 expected_hash: CryptoHash,
446 expected_round: Round,
447 },
448
449 #[error("signer error: {0}")]
450 Signer(#[source] Box<dyn signer::Error>),
451
452 #[error("Cannot revoke the current epoch {0}")]
453 CannotRevokeCurrentEpoch(Epoch),
454
455 #[error("Epoch is already revoked")]
456 EpochAlreadyRevoked,
457
458 #[error("Failed to download missing sender blocks from chain {chain_id} at height {height}")]
459 CannotDownloadMissingSenderBlock {
460 chain_id: ChainId,
461 height: BlockHeight,
462 },
463
464 #[error(
465 "A different block was already committed at this height. \
466 The committed certificate hash is {0}"
467 )]
468 Conflict(CryptoHash),
469
470 #[error(
471 "Execution outcome mismatch: AutoRetry and committed execution produced \
472 different outcomes for the same block"
473 )]
474 ExecutionOutcomeMismatch,
475}
476
477impl From<Infallible> for Error {
478 fn from(infallible: Infallible) -> Self {
479 match infallible {}
480 }
481}
482
483impl Error {
484 pub fn signer_failure(err: impl signer::Error + 'static) -> Self {
486 Self::Signer(Box::new(err))
487 }
488
489 pub fn error_type(&self) -> String {
493 match self {
494 Error::LocalNodeError(local_node_error) => local_node_error.error_type(),
495 Error::ChainError(chain_error) => chain_error.error_type(),
496 other => {
497 let variant: &'static str = other.into();
498 format!("ChainClientError::{variant}")
499 }
500 }
501 }
502}
503
504impl<Env: Environment> ChainClient<Env> {
505 pub fn new(
507 client: Arc<Client<Env>>,
508 chain_id: ChainId,
509 options: Options,
510 initial_block_hash: Option<CryptoHash>,
511 initial_next_block_height: BlockHeight,
512 preferred_owner: Option<AccountOwner>,
513 timing_sender: Option<mpsc::UnboundedSender<(u64, TimingType)>>,
514 ) -> Self {
515 ChainClient {
516 client,
517 chain_id,
518 options,
519 preferred_owner,
520 initial_block_hash,
521 initial_next_block_height,
522 timing_sender,
523 skipped_origins: Arc::new(papaya::HashSet::new()),
524 }
525 }
526
527 pub fn is_follow_only(&self) -> bool {
529 self.client.is_chain_follow_only(self.chain_id)
530 }
531
532 #[instrument(level = "trace", skip(self))]
536 fn proposal_mutex(&self) -> Arc<tokio::sync::Mutex<Option<PendingProposal>>> {
537 self.client
538 .chains
539 .pin()
540 .get(&self.chain_id)
541 .expect("Chain client constructed for invalid chain")
542 .proposal_mutex()
543 }
544
545 #[instrument(level = "trace", skip(self))]
547 pub async fn pending_proposal(&self) -> Option<PendingProposal> {
548 self.proposal_mutex().lock().await.clone()
549 }
550
551 #[instrument(level = "trace", skip(self))]
553 pub fn signer(&self) -> &impl Signer {
554 self.client.signer()
555 }
556
557 pub async fn has_key_for(&self, owner: &AccountOwner) -> Result<bool, Error> {
559 self.client.has_key_for(owner).await
560 }
561
562 #[instrument(level = "trace", skip(self))]
564 pub fn options_mut(&mut self) -> &mut Options {
565 &mut self.options
566 }
567
568 #[instrument(level = "trace", skip(self))]
570 pub fn options(&self) -> &Options {
571 &self.options
572 }
573
574 #[instrument(level = "trace", skip(self))]
576 pub fn chain_id(&self) -> ChainId {
577 self.chain_id
578 }
579
580 pub fn timing_sender(&self) -> Option<mpsc::UnboundedSender<(u64, TimingType)>> {
582 self.timing_sender.clone()
583 }
584
585 #[instrument(level = "trace", skip(self))]
587 pub fn admin_chain_id(&self) -> ChainId {
588 self.client.admin_chain_id
589 }
590
591 #[instrument(level = "trace", skip(self))]
593 pub fn preferred_owner(&self) -> Option<AccountOwner> {
594 self.preferred_owner
595 }
596
597 #[instrument(level = "trace", skip(self))]
599 pub fn set_preferred_owner(&mut self, preferred_owner: AccountOwner) {
600 self.preferred_owner = Some(preferred_owner);
601 }
602
603 #[instrument(level = "trace")]
605 pub async fn chain_state_view(
606 &self,
607 ) -> Result<crate::worker::ChainStateViewReadGuard<Env::Storage>, LocalNodeError> {
608 self.client.local_node.chain_state_view(self.chain_id).await
609 }
610
611 #[instrument(level = "trace", skip(self))]
613 pub async fn event_stream_publishers(
614 &self,
615 ) -> Result<BTreeMap<ChainId, BTreeSet<StreamId>>, LocalNodeError> {
616 let subscriptions = self
617 .client
618 .local_node
619 .get_event_subscriptions(self.chain_id)
620 .await?;
621 let mut publishers = subscriptions.into_iter().fold(
622 BTreeMap::<ChainId, BTreeSet<StreamId>>::new(),
623 |mut map, ((chain_id, stream_id), _)| {
624 map.entry(chain_id).or_default().insert(stream_id);
625 map
626 },
627 );
628 if self.chain_id != self.client.admin_chain_id {
629 publishers.insert(
630 self.client.admin_chain_id,
631 vec![
632 StreamId::system(EPOCH_STREAM_NAME),
633 StreamId::system(REMOVED_EPOCH_STREAM_NAME),
634 ]
635 .into_iter()
636 .collect(),
637 );
638 }
639 Ok(publishers)
640 }
641
642 #[instrument(level = "trace")]
644 pub fn subscribe(&self) -> Result<NotificationStream, LocalNodeError> {
645 self.subscribe_to(self.chain_id)
646 }
647
648 #[instrument(level = "trace")]
650 pub fn subscribe_to(&self, chain_id: ChainId) -> Result<NotificationStream, LocalNodeError> {
651 Ok(Box::pin(UnboundedReceiverStream::new(
652 self.client.notifier.subscribe(vec![chain_id]),
653 )))
654 }
655
656 #[instrument(level = "trace")]
658 pub fn storage_client(&self) -> &Env::Storage {
659 self.client.storage_client()
660 }
661
662 #[instrument(level = "trace")]
664 pub async fn chain_info(&self) -> Result<Box<ChainInfo>, LocalNodeError> {
665 let query = ChainInfoQuery::new(self.chain_id);
666 let response = self
667 .client
668 .local_node
669 .handle_chain_info_query(query)
670 .await?;
671 Ok(response.info)
672 }
673
674 #[instrument(level = "trace")]
676 pub async fn chain_info_with_manager_values(&self) -> Result<Box<ChainInfo>, LocalNodeError> {
677 let query = ChainInfoQuery::new(self.chain_id).with_manager_values();
678 let response = self
679 .client
680 .local_node
681 .handle_chain_info_query(query)
682 .await?;
683 Ok(response.info)
684 }
685
686 async fn consensus_state_snapshot(&self) -> Result<ConsensusStateSnapshot, Error> {
695 let info = self.chain_info_with_manager_values().await?;
696 let lock_round = info
697 .manager
698 .requested_locking
699 .as_deref()
700 .map(LockingBlock::round);
701 let timeout_round = info.manager.timeout.as_deref().map(|cert| cert.round);
702 Ok(ConsensusStateSnapshot {
703 next_block_height: info.next_block_height,
704 current_round: info.manager.current_round,
705 lock_round,
706 timeout_round,
707 })
708 }
709
710 pub async fn get_chain_description(&self) -> Result<ChainDescription, Error> {
712 self.client.get_chain_description(self.chain_id).await
713 }
714
715 pub async fn get_application_description(
719 &self,
720 application_id: ApplicationId,
721 ) -> Result<ApplicationDescription, Error> {
722 self.client
723 .get_application_description(application_id)
724 .await
725 }
726
727 #[instrument(level = "trace")]
730 async fn pending_message_bundles(&self) -> Result<Vec<IncomingBundle>, Error> {
731 if self.options.message_policy.is_ignore() {
732 return Ok(Vec::new());
734 }
735
736 let query = ChainInfoQuery::new(self.chain_id).with_pending_message_bundles();
737 let info = self
738 .client
739 .local_node
740 .handle_chain_info_query(query)
741 .await?
742 .info;
743 if self.preferred_owner.is_some_and(|owner| {
744 info.manager
745 .ownership
746 .is_super_owner_no_regular_owners(&owner)
747 }) {
748 ensure!(
750 info.next_block_height >= self.initial_next_block_height,
751 Error::WalletSynchronizationError
752 );
753 }
754
755 let skipped = self.skipped_origins.pin();
756 let mut bundles = info
757 .requested_pending_message_bundles
758 .into_iter()
759 .filter_map(|bundle| bundle.apply_policy(&self.options.message_policy))
760 .filter(|bundle| !skipped.contains(&bundle.origin))
761 .collect::<Vec<_>>();
762 let priority_origins = &self.options.priority_bundle_origins;
763 bundles.sort_by(|a, b| {
764 let a_priority = priority_origins.contains(&a.origin);
765 let b_priority = priority_origins.contains(&b.origin);
766 b_priority
767 .cmp(&a_priority)
768 .then(a.bundle.timestamp.cmp(&b.bundle.timestamp))
769 });
770 bundles.truncate(self.options.max_pending_message_bundles);
771 Ok(bundles)
772 }
773
774 #[instrument(level = "trace")]
775 async fn collect_stream_updates(&self) -> Result<Vec<Operation>, Error> {
776 let subscription_map = self
777 .client
778 .local_node
779 .get_event_subscriptions(self.chain_id)
780 .await?;
781 let futures = subscription_map
782 .into_iter()
783 .filter(|((chain_id, _), _)| {
784 self.options
785 .message_policy
786 .restrict_chain_ids_to
787 .as_ref()
788 .is_none_or(|chain_set| chain_set.contains(chain_id))
789 })
790 .filter(|((_, stream_id), _)| {
791 self.options
792 .message_policy
793 .process_events_from_application_ids
794 .as_ref()
795 .is_none_or(|app_set| app_set.contains(&stream_id.application_id))
796 })
797 .map(|((chain_id, stream_id), subscriptions)| {
798 let client = self.client.clone();
799 async move {
800 let counts = client
801 .local_node
802 .get_stream_indices(chain_id, stream_id.clone())
803 .await?;
804 let (first_index, next_index) = (counts.first_index, counts.next_index);
805 if next_index <= subscriptions.min_next_index {
806 return Ok::<_, Error>(Vec::new());
807 }
808 Ok(subscriptions
809 .applications
810 .into_iter()
811 .filter(|(_, app_index)| *app_index < next_index)
812 .map(|(application_id, _)| {
813 SystemOperation::UpdateStream {
814 application_id,
815 chain_id,
816 stream_id: stream_id.clone(),
817 first_index,
818 next_index,
819 }
820 .into()
821 })
822 .collect::<Vec<Operation>>())
823 }
824 });
825 Ok(futures::stream::iter(futures)
826 .buffer_unordered(self.options.max_joined_tasks)
827 .try_collect::<Vec<_>>()
828 .await?
829 .into_iter()
830 .flatten()
831 .collect::<Vec<_>>())
832 }
833
834 #[instrument(level = "trace")]
836 pub async fn local_committee(&self) -> Result<Arc<Committee>, Error> {
837 let info = match self.client.local_node.chain_info(self.chain_id).await {
838 Ok(info) => info,
839 Err(LocalNodeError::BlobsNotFound(_)) => {
840 self.synchronize_chain_state(self.chain_id).await?;
841 self.client.local_node.chain_info(self.chain_id).await?
842 }
843 Err(err) => return Err(err.into()),
844 };
845 let hash = info
846 .committee_hash
847 .ok_or(LocalNodeError::InactiveChain(self.chain_id))?;
848 Ok(self
849 .storage_client()
850 .get_or_load_committee_by_hash(hash)
851 .await
852 .map_err(LocalNodeError::from)?)
853 }
854
855 #[instrument(level = "trace")]
857 pub async fn admin_committee(&self) -> Result<(Epoch, Arc<Committee>), LocalNodeError> {
858 self.client.admin_committee().await
859 }
860
861 #[instrument(level = "trace")]
865 pub async fn identity(&self) -> Result<AccountOwner, Error> {
866 let Some(preferred_owner) = self.preferred_owner else {
867 return Err(Error::NoAccountKeyConfigured(self.chain_id));
868 };
869 let manager = self.chain_info().await?.manager;
870 ensure!(
871 manager.ownership.is_active(),
872 LocalNodeError::InactiveChain(self.chain_id)
873 );
874 let fallback_owners = if manager.ownership.has_fallback() {
875 self.local_committee()
876 .await?
877 .account_keys_and_weights()
878 .map(|(key, _)| AccountOwner::from(key))
879 .collect()
880 } else {
881 BTreeSet::new()
882 };
883
884 let is_owner = manager
885 .ownership
886 .can_propose_in_multi_leader_round(&preferred_owner)
887 || fallback_owners.contains(&preferred_owner);
888
889 if !is_owner {
890 warn!(
891 chain_id = %self.chain_id,
892 ownership = ?manager.ownership,
893 ?fallback_owners,
894 ?preferred_owner,
895 "The preferred owner is not configured as an owner of this chain",
896 );
897 return Err(Error::NotAnOwner(self.chain_id));
898 }
899
900 let has_signer = self.has_key_for(&preferred_owner).await?;
901
902 if !has_signer {
903 warn!(%self.chain_id, ?preferred_owner,
904 "Chain is one of the owners but its Signer instance doesn't contain the key",
905 );
906 return Err(Error::CannotFindKeyForChain(self.chain_id));
907 }
908
909 Ok(preferred_owner)
910 }
911
912 #[instrument(level = "trace")]
920 pub async fn prepare_for_owner(&self, owner: AccountOwner) -> Result<Box<ChainInfo>, Error> {
921 ensure!(
922 self.has_key_for(&owner).await?,
923 Error::CannotFindKeyForChain(self.chain_id)
924 );
925 self.client
927 .get_chain_description_blob(self.chain_id)
928 .await?;
929
930 let info = self.chain_info().await?;
932
933 ensure!(
935 info.manager
936 .ownership
937 .can_propose_in_multi_leader_round(&owner),
938 Error::NotAnOwner(self.chain_id)
939 );
940
941 Ok(info)
942 }
943
944 #[instrument(level = "trace")]
947 pub async fn prepare_chain(&self) -> Result<Box<ChainInfo>, Error> {
948 #[cfg(with_metrics)]
949 let _latency = super::metrics::PREPARE_CHAIN_LATENCY.measure_latency();
950
951 let mut info = self.synchronize_to_known_height().await?;
952
953 if self.preferred_owner.is_none_or(|owner| {
954 !info
955 .manager
956 .ownership
957 .is_super_owner_no_regular_owners(&owner)
958 }) {
959 info = self.client.synchronize_chain_state(self.chain_id).await?;
963 }
964
965 if info.epoch > self.client.admin_committee().await?.0 {
966 self.client
967 .synchronize_chain_state(self.client.admin_chain_id)
968 .await?;
969 }
970
971 Ok(info)
972 }
973
974 async fn synchronize_to_known_height(&self) -> Result<Box<ChainInfo>, Error> {
979 let info = self
980 .client
981 .download_certificates(self.chain_id, self.initial_next_block_height)
982 .await?;
983 if info.next_block_height == self.initial_next_block_height {
984 ensure!(
986 self.initial_block_hash == info.block_hash,
987 Error::InternalError("Invalid chain of blocks in local node")
988 );
989 }
990 Ok(info)
991 }
992
993 #[instrument(level = "trace", skip(old_committee, latest_certificate))]
995 pub async fn update_validators(
996 &self,
997 old_committee: Option<&Committee>,
998 latest_certificate: Option<CacheArc<ConfirmedBlockCertificate>>,
999 ) -> Result<(), Error> {
1000 let update_validators_start = linera_base::time::Instant::now();
1001 if let Some(old_committee) = old_committee {
1003 self.communicate_chain_updates(old_committee, latest_certificate.clone())
1004 .await?
1005 };
1006 if let Ok(new_committee) = self.local_committee().await {
1007 if old_committee.is_none_or(|old| *new_committee != *old) {
1008 self.communicate_chain_updates(&new_committee, latest_certificate)
1011 .await?;
1012 }
1013 }
1014 self.send_timing(update_validators_start, TimingType::UpdateValidators);
1015 Ok(())
1016 }
1017
1018 #[instrument(level = "trace", skip(committee))]
1020 pub async fn communicate_chain_updates(
1021 &self,
1022 committee: &Committee,
1023 latest_certificate: Option<CacheArc<ConfirmedBlockCertificate>>,
1024 ) -> Result<(), Error> {
1025 let delivery = self.options.cross_chain_message_delivery;
1026 let height = self.chain_info().await?.next_block_height;
1027 self.client
1028 .communicate_chain_updates(
1029 committee,
1030 self.chain_id,
1031 height,
1032 delivery,
1033 latest_certificate,
1034 )
1035 .await
1036 }
1037
1038 async fn synchronize_publisher_chains(&self) -> Result<(), Error> {
1042 let subscriptions = self
1043 .client
1044 .local_node
1045 .get_event_subscriptions(self.chain_id)
1046 .await?;
1047 let mut streams_by_chain = BTreeMap::<ChainId, BTreeSet<StreamId>>::new();
1049 for ((chain_id, stream_id), _) in &subscriptions {
1050 if *chain_id != self.chain_id {
1051 streams_by_chain
1052 .entry(*chain_id)
1053 .or_default()
1054 .insert(stream_id.clone());
1055 }
1056 }
1057 let admin_chain_id = self.client.admin_chain_id;
1059 if admin_chain_id != self.chain_id {
1060 self.client.synchronize_chain_state(admin_chain_id).await?;
1061 }
1062 let (_, committee) = self.admin_committee().await?;
1064 let nodes = self.client.make_nodes(&committee)?;
1065 let tasks = streams_by_chain
1066 .into_iter()
1067 .filter(|(chain_id, _)| *chain_id != admin_chain_id)
1068 .map(|(chain_id, stream_ids)| {
1069 self.sync_publisher_chain_events(chain_id, stream_ids, &nodes, &committee)
1070 })
1071 .collect::<Vec<_>>();
1072 stream::iter(tasks)
1073 .buffer_unordered(self.options.max_joined_tasks)
1074 .collect::<Vec<_>>()
1075 .await
1076 .into_iter()
1077 .collect::<Result<Vec<_>, _>>()?;
1078 Ok(())
1079 }
1080
1081 async fn sync_publisher_chain_events(
1088 &self,
1089 publisher_chain_id: ChainId,
1090 stream_ids: BTreeSet<StreamId>,
1091 nodes: &[RemoteNode<Env::ValidatorNode>],
1092 committee: &Committee,
1093 ) -> Result<(), Error> {
1094 let stream_ids_ref = &stream_ids;
1095 communicate_with_quorum(
1096 nodes,
1097 committee,
1098 |_: &()| (),
1099 |remote_node| async move {
1100 self.client
1101 .sync_events_from_node(publisher_chain_id, stream_ids_ref, &remote_node)
1102 .await
1103 },
1104 self.options.quorum_grace_period,
1105 )
1106 .await?;
1107 Ok(())
1108 }
1109
1110 #[instrument(level = "debug", skip(self), fields(chain_id = %self.chain_id))]
1119 pub async fn find_received_certificates(&self) -> Result<(), Error> {
1120 debug!("starting find_received_certificates");
1121 let sync_start = Instant::now();
1122 #[cfg(with_metrics)]
1123 let _latency = super::metrics::FIND_RECEIVED_CERTIFICATES_LATENCY.measure_latency();
1124 let chain_id = self.chain_id;
1126 let (_, committee) = self.admin_committee().await?;
1127 let nodes = self.client.make_nodes(&committee)?;
1128
1129 let trackers = self
1130 .client
1131 .local_node
1132 .get_received_certificate_trackers(chain_id)
1133 .await?;
1134
1135 trace!("find_received_certificates: read trackers");
1136
1137 let received_log_batches = Arc::new(std::sync::Mutex::new(Vec::new()));
1138 let result = communicate_with_quorum(
1140 &nodes,
1141 &committee,
1142 |_| (),
1143 |remote_node| {
1144 let client = &self.client;
1145 let tracker = trackers.get(&remote_node.public_key).copied().unwrap_or(0);
1146 let received_log_batches = Arc::clone(&received_log_batches);
1147 Box::pin(async move {
1148 let batch = client
1149 .get_received_log_from_validator(chain_id, &remote_node, tracker)
1150 .await?;
1151 let mut batches = received_log_batches.lock().unwrap();
1152 batches.push((remote_node.public_key, batch));
1153 Ok(())
1154 })
1155 },
1156 self.options.quorum_grace_period,
1157 )
1158 .await;
1159
1160 if let Err(error) = result {
1161 error!(
1162 %error,
1163 "Failed to synchronize received_logs from at least a quorum of validators",
1164 );
1165 }
1166
1167 let received_logs: Vec<_> = {
1168 let mut received_log_batches = received_log_batches.lock().unwrap();
1169 std::mem::take(received_log_batches.as_mut())
1170 };
1171
1172 let received_logs_total = received_logs.iter().map(|x| x.1.len()).sum::<usize>();
1173 let entries_per_validator = received_logs
1177 .iter()
1178 .map(|(public_key, batch)| (*public_key, batch.len()))
1179 .collect::<Vec<_>>();
1180 #[cfg(with_metrics)]
1181 super::metrics::FIND_RECEIVED_CERTIFICATES_LOG_ENTRIES
1182 .with_label_values(&[])
1183 .observe(received_logs_total as f64);
1184
1185 debug!(
1186 received_logs_len = %received_logs.len(),
1187 %received_logs_total,
1188 "collected received logs"
1189 );
1190
1191 let (received_logs, mut validator_trackers) = {
1192 (
1193 ReceivedLogs::from_received_result(received_logs.clone()),
1194 ValidatorTrackers::new(received_logs, &trackers),
1195 )
1196 };
1197
1198 #[cfg(with_metrics)]
1199 {
1200 super::metrics::FIND_RECEIVED_CERTIFICATES_SENDER_CHAINS
1201 .with_label_values(&[])
1202 .observe(received_logs.num_chains() as f64);
1203 super::metrics::SENDER_CERTIFICATES_DISCOVERED_TOTAL
1204 .inc_by(received_logs.num_certs() as u64);
1205 }
1206
1207 debug!(
1208 num_chains = %received_logs.num_chains(),
1209 num_certs = %received_logs.num_certs(),
1210 "find_received_certificates: total number of chains and certificates to sync",
1211 );
1212
1213 let num_certs = received_logs.num_certs();
1214 let is_large_sync = num_certs >= LARGE_RECEIVED_CERTIFICATE_SYNC_THRESHOLD;
1215 if is_large_sync {
1216 let entries_per_validator = entries_per_validator
1218 .iter()
1219 .map(|(public_key, entries)| {
1220 let address = nodes
1221 .iter()
1222 .find(|node| node.public_key == *public_key)
1223 .map_or_else(|| public_key.to_string(), |node| node.address());
1224 (address, *entries)
1225 })
1226 .collect::<Vec<_>>();
1227 info!(
1228 %chain_id,
1229 num_chains = %received_logs.num_chains(),
1230 %num_certs,
1231 num_log_entries = %received_logs_total,
1232 ?entries_per_validator,
1233 "starting a large sync of received certificates",
1234 );
1235 }
1236 let max_blocks_per_chain =
1237 self.options.sender_certificate_download_batch_size / self.options.max_joined_tasks * 2;
1238 let mut num_synced_certs = 0;
1239 let mut next_progress_message = RECEIVED_CERTIFICATE_SYNC_PROGRESS_INTERVAL;
1240 for received_log in received_logs.into_batches(
1241 self.options.sender_certificate_download_batch_size,
1242 max_blocks_per_chain,
1243 ) {
1244 num_synced_certs += received_log.num_certs();
1245 validator_trackers = self
1246 .receive_sender_certificates(received_log, validator_trackers, &nodes)
1247 .await?;
1248
1249 self.update_received_certificate_trackers(&validator_trackers)
1250 .await;
1251
1252 if is_large_sync && num_synced_certs >= next_progress_message {
1253 info!(
1254 %chain_id,
1255 %num_synced_certs,
1256 %num_certs,
1257 "received-certificate sync in progress",
1258 );
1259 next_progress_message =
1260 num_synced_certs + RECEIVED_CERTIFICATE_SYNC_PROGRESS_INTERVAL;
1261 }
1262 }
1263
1264 if is_large_sync {
1265 info!(
1266 %chain_id,
1267 %num_certs,
1268 elapsed_secs = %sync_start.elapsed().as_secs(),
1269 "finished a large sync of received certificates",
1270 );
1271 }
1272
1273 trace!("find_received_certificates finished");
1274
1275 Ok(())
1276 }
1277
1278 async fn update_received_certificate_trackers(&self, trackers: &ValidatorTrackers) {
1279 let updated_trackers = trackers.to_map();
1280 trace!(?updated_trackers, "updated tracker values");
1281
1282 if let Err(error) = self
1284 .client
1285 .local_node
1286 .update_received_certificate_trackers(self.chain_id, updated_trackers)
1287 .await
1288 {
1289 error!(
1290 chain_id = %self.chain_id,
1291 %error,
1292 "Failed to update the certificate trackers",
1293 );
1294 }
1295 }
1296
1297 async fn receive_sender_certificates(
1300 &self,
1301 mut received_logs: ReceivedLogs,
1302 mut validator_trackers: ValidatorTrackers,
1303 nodes: &[RemoteNode<Env::ValidatorNode>],
1304 ) -> Result<ValidatorTrackers, Error> {
1305 debug!(
1306 num_chains = %received_logs.num_chains(),
1307 num_certs = %received_logs.num_certs(),
1308 "receive_sender_certificates: number of chains and certificates to sync",
1309 );
1310
1311 let local_next_heights = self
1313 .client
1314 .local_node
1315 .next_outbox_heights(received_logs.chains(), self.chain_id)
1316 .await?;
1317
1318 validator_trackers.filter_out_already_known(&mut received_logs, &local_next_heights);
1319
1320 #[cfg(with_metrics)]
1321 super::metrics::SENDER_CERTIFICATES_MISSING_TOTAL.inc_by(received_logs.num_certs() as u64);
1322
1323 debug!(
1324 remaining_total_certificates = %received_logs.num_certs(),
1325 "receive_sender_certificates: computed remote_heights"
1326 );
1327
1328 let mut other_sender_chains = Vec::new();
1329 let (sender, mut receiver) = mpsc::unbounded_channel::<ChainAndHeight>();
1330
1331 let cert_futures = received_logs.heights_per_chain().into_iter().filter_map({
1332 let received_logs = &received_logs;
1333 let other_sender_chains = &mut other_sender_chains;
1334
1335 move |(sender_chain_id, remote_heights)| {
1336 if remote_heights.is_empty() {
1337 other_sender_chains.push(sender_chain_id);
1341 return None;
1342 };
1343 let remote_heights = remote_heights.into_iter().collect::<Vec<_>>();
1344 let sender = sender.clone();
1345 let client = self.client.clone();
1346 let nodes = nodes.to_vec();
1347 Some(async move {
1348 client
1349 .download_and_process_sender_chain(
1350 sender_chain_id,
1351 &nodes,
1352 received_logs,
1353 remote_heights,
1354 sender,
1355 )
1356 .await
1357 })
1358 }
1359 });
1360
1361 future::join(
1362 stream::iter(cert_futures)
1363 .buffer_unordered(self.options.max_joined_tasks)
1364 .collect::<()>(),
1365 async {
1366 while let Some(chain_and_height) = receiver.recv().await {
1367 validator_trackers.downloaded_cert(chain_and_height);
1368 }
1369 },
1370 )
1371 .await;
1372
1373 debug!(
1374 num_other_chains = %other_sender_chains.len(),
1375 "receive_sender_certificates: processing certificates finished"
1376 );
1377
1378 self.retry_pending_cross_chain_requests_from_sender_chains(nodes, other_sender_chains)
1382 .await;
1383
1384 debug!("receive_sender_certificates: finished processing other_sender_chains");
1385
1386 Ok(validator_trackers)
1387 }
1388
1389 async fn retry_pending_cross_chain_requests_from_sender_chains(
1393 &self,
1394 nodes: &[RemoteNode<Env::ValidatorNode>],
1395 other_sender_chains: Vec<ChainId>,
1396 ) {
1397 let stream = other_sender_chains
1398 .into_iter()
1399 .map(|chain_id| async move {
1400 if let Err(error) = match self
1401 .client
1402 .retry_pending_cross_chain_requests(chain_id)
1403 .await
1404 {
1405 Ok(()) => Ok(()),
1406 Err(LocalNodeError::BlobsNotFound(blob_ids)) => {
1407 if let Err(error) = self
1408 .client
1409 .update_local_node_with_blobs_from(blob_ids.clone(), nodes)
1410 .await
1411 {
1412 error!(
1413 ?blob_ids,
1414 %error,
1415 "Error while attempting to download blobs during retrying outgoing \
1416 messages"
1417 );
1418 }
1419 self.client
1420 .retry_pending_cross_chain_requests(chain_id)
1421 .await
1422 }
1423 err => err,
1424 } {
1425 error!(
1426 %chain_id,
1427 %error,
1428 "Failed to retry outgoing messages from chain"
1429 );
1430 }
1431 })
1432 .collect::<FuturesUnordered<_>>();
1433 stream.for_each(future::ready).await;
1434 }
1435
1436 #[instrument(level = "trace")]
1438 pub async fn transfer(
1439 &self,
1440 owner: AccountOwner,
1441 amount: Amount,
1442 recipient: Account,
1443 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1444 self.execute_operation(SystemOperation::Transfer {
1446 owner,
1447 recipient,
1448 amount,
1449 })
1450 .await
1451 }
1452
1453 #[instrument(level = "trace")]
1456 pub async fn read_data_blob(
1457 &self,
1458 hash: CryptoHash,
1459 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1460 let blob_id = BlobId {
1461 hash,
1462 blob_type: BlobType::Data,
1463 };
1464 self.execute_operation(SystemOperation::VerifyBlob { blob_id })
1465 .await
1466 }
1467
1468 #[instrument(level = "trace")]
1470 pub async fn claim(
1471 &self,
1472 owner: AccountOwner,
1473 target_id: ChainId,
1474 recipient: Account,
1475 amount: Amount,
1476 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1477 self.execute_operation(SystemOperation::Claim {
1478 owner,
1479 target_id,
1480 recipient,
1481 amount,
1482 })
1483 .await
1484 }
1485
1486 #[instrument(level = "trace")]
1489 pub async fn request_leader_timeout(&self) -> Result<TimeoutCertificate, Error> {
1490 let chain_id = self.chain_id;
1491 let info = self.chain_info().await?;
1492 let committee = self.local_committee().await?;
1493 let height = info.next_block_height;
1494 let round = info.manager.current_round;
1495 let action = CommunicateAction::RequestTimeout {
1496 height,
1497 round,
1498 chain_id,
1499 };
1500 let value = Timeout::new(chain_id, height, info.epoch);
1501 let certificate = Box::new(
1502 self.client
1503 .communicate_chain_action(&committee, action, value)
1504 .await?,
1505 );
1506 self.client
1507 .handle_certificate::<Timeout>(*certificate.clone())
1508 .await?;
1509 self.client
1511 .communicate_chain_updates(
1512 &committee,
1513 chain_id,
1514 height,
1515 CrossChainMessageDelivery::NonBlocking,
1516 None,
1517 )
1518 .await?;
1519 Ok(*certificate)
1520 }
1521
1522 #[instrument(level = "trace", skip_all)]
1524 pub async fn synchronize_chain_state(
1525 &self,
1526 chain_id: ChainId,
1527 ) -> Result<Box<ChainInfo>, Error> {
1528 self.client.synchronize_chain_state(chain_id).await
1529 }
1530
1531 #[instrument(level = "trace", skip_all)]
1534 pub async fn synchronize_chain_state_from_committee(
1535 &self,
1536 committee: Arc<Committee>,
1537 ) -> Result<Box<ChainInfo>, Error> {
1538 self.client
1539 .synchronize_chain_from_committee(self.chain_id, committee)
1540 .await
1541 }
1542
1543 #[instrument(level = "trace", skip(operations, blobs))]
1545 pub async fn execute_operations(
1546 &self,
1547 operations: Vec<Operation>,
1548 blobs: Vec<Blob>,
1549 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1550 let timing_start = linera_base::time::Instant::now();
1551
1552 let result = loop {
1553 let execute_block_start = linera_base::time::Instant::now();
1554 match Box::pin(self.execute_block(operations.clone(), blobs.clone())).await {
1556 Ok(ClientOutcome::Committed(certificate)) => {
1557 self.send_timing(execute_block_start, TimingType::ExecuteBlock);
1558 break Ok(ClientOutcome::Committed(certificate));
1559 }
1560 Ok(ClientOutcome::WaitForTimeout(timeout)) => {
1561 break Ok(ClientOutcome::WaitForTimeout(timeout));
1562 }
1563 Ok(ClientOutcome::Conflict(certificate)) => {
1564 info!(
1565 height = %certificate.block().header.height,
1566 "Another block was committed."
1567 );
1568 break Ok(ClientOutcome::Conflict(certificate));
1569 }
1570 Err(Error::CommunicationError(CommunicationError::Trusted(
1571 NodeError::UnexpectedBlockHeight {
1572 expected_block_height,
1573 found_block_height,
1574 },
1575 ))) if expected_block_height > found_block_height => {
1576 tracing::info!(
1577 chain_id = %self.chain_id,
1578 "Local state is outdated; synchronizing chain"
1579 );
1580 self.synchronize_chain_state(self.chain_id).await?;
1581 }
1582 Err(err) => return Err(err),
1583 };
1584 };
1585
1586 self.send_timing(timing_start, TimingType::ExecuteOperations);
1587
1588 result
1589 }
1590
1591 pub async fn execute_operation(
1593 &self,
1594 operation: impl Into<Operation>,
1595 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1596 self.execute_operations(vec![operation.into()], vec![])
1597 .await
1598 }
1599
1600 #[instrument(level = "trace", skip(operations, blobs))]
1604 async fn execute_block(
1605 &self,
1606 operations: Vec<Operation>,
1607 blobs: Vec<Blob>,
1608 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1609 #[cfg(with_metrics)]
1610 let _latency = super::metrics::EXECUTE_BLOCK_LATENCY.measure_latency();
1611
1612 let result = self.try_execute_block(operations, blobs).await;
1613 if let Err(error) = &result {
1614 let error_type = error.error_type();
1615 #[cfg(with_metrics)]
1616 super::metrics::BLOCK_STAGING_FAILURES_TOTAL
1617 .with_label_values(&[error_type.as_str()])
1618 .inc();
1619 info!(chain_id = %self.chain_id, %error_type, "Block staging failed");
1620 }
1621 result
1622 }
1623
1624 async fn try_execute_block(
1625 &self,
1626 operations: Vec<Operation>,
1627 blobs: Vec<Blob>,
1628 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1629 let mutex = self.proposal_mutex();
1630 let lock_start = linera_base::time::Instant::now();
1631 let mut proposal_guard = mutex.lock_owned().await;
1632 tracing::debug!(
1633 chain_id = %self.chain_id,
1634 lock_wait_ms = lock_start.elapsed().as_millis(),
1635 "acquired proposal_mutex in execute_block"
1636 );
1637 match self
1643 .process_pending_block_without_prepare(&mut proposal_guard)
1644 .await?
1645 {
1646 ClientOutcome::Committed(Some(certificate)) => {
1647 return Ok(self.classify_committed(certificate, &operations));
1648 }
1649 ClientOutcome::WaitForTimeout(timeout) => {
1650 return Ok(ClientOutcome::WaitForTimeout(timeout))
1651 }
1652 ClientOutcome::Conflict(certificate) => {
1653 return Ok(ClientOutcome::Conflict(certificate))
1654 }
1655 ClientOutcome::Committed(None) => {}
1656 }
1657
1658 loop {
1659 let transactions = self
1664 .prepend_epochs_messages_and_events(operations.clone())
1665 .await?;
1666
1667 if transactions.is_empty() {
1668 return Err(Error::LocalNodeError(LocalNodeError::WorkerError(
1669 WorkerError::ChainError(Box::new(ChainError::EmptyBlock)),
1670 )));
1671 }
1672
1673 self.new_pending_block(transactions, blobs.clone(), &mut proposal_guard)
1674 .await?;
1675
1676 match self
1677 .process_pending_block_without_prepare(&mut proposal_guard)
1678 .await?
1679 {
1680 ClientOutcome::Committed(Some(certificate)) => {
1681 return Ok(self.classify_committed(certificate, &operations));
1682 }
1683 ClientOutcome::Committed(None) => {
1697 tracing::debug!(
1698 chain_id = %self.chain_id,
1699 "pending proposal cleared without committing ours; re-staging and retrying"
1700 );
1701 continue;
1702 }
1703 ClientOutcome::WaitForTimeout(timeout) => {
1704 return Ok(ClientOutcome::WaitForTimeout(timeout));
1705 }
1706 ClientOutcome::Conflict(certificate) => {
1707 return Ok(ClientOutcome::Conflict(certificate));
1708 }
1709 }
1710 }
1711 }
1712
1713 fn classify_committed(
1717 &self,
1718 certificate: ConfirmedBlockCertificate,
1719 operations: &[Operation],
1720 ) -> ClientOutcome<ConfirmedBlockCertificate> {
1721 let block = certificate.block();
1722 if self.preferred_owner.is_none()
1723 || block.header.authenticated_owner != self.preferred_owner
1724 {
1725 return ClientOutcome::Conflict(Box::new(certificate));
1726 }
1727 let mut operations_iter = operations.iter().peekable();
1728 for tx in &block.body.transactions {
1729 let is_expected = match tx {
1730 Transaction::ReceiveMessages(_) => true,
1731 Transaction::ExecuteOperation(op) if Some(&op) == operations_iter.peek() => {
1732 operations_iter.next();
1733 true
1734 }
1735 Transaction::ExecuteOperation(Operation::System(op)) => matches!(
1736 **op,
1737 SystemOperation::ProcessNewEpoch(_) | SystemOperation::UpdateStream { .. }
1738 ),
1739 Transaction::ExecuteOperation(Operation::User { .. }) => false,
1740 };
1741 if !is_expected {
1742 return ClientOutcome::Conflict(Box::new(certificate));
1743 }
1744 }
1745 if operations_iter.next().is_some() {
1746 ClientOutcome::Conflict(Box::new(certificate))
1747 } else {
1748 ClientOutcome::Committed(certificate)
1749 }
1750 }
1751
1752 #[instrument(level = "trace", skip(operations))]
1758 async fn prepend_epochs_messages_and_events(
1759 &self,
1760 operations: Vec<Operation>,
1761 ) -> Result<Vec<Transaction>, Error> {
1762 let incoming_bundles = self.pending_message_bundles().await?;
1763 let stream_updates = self.collect_stream_updates().await?;
1764 Ok(self
1765 .next_epoch_change()
1766 .await?
1767 .into_iter()
1768 .map(Transaction::ExecuteOperation)
1769 .chain(
1770 incoming_bundles
1771 .into_iter()
1772 .map(Transaction::ReceiveMessages),
1773 )
1774 .chain(
1775 stream_updates
1776 .into_iter()
1777 .map(Transaction::ExecuteOperation),
1778 )
1779 .chain(operations.into_iter().map(Transaction::ExecuteOperation))
1780 .collect::<Vec<_>>())
1781 }
1782
1783 #[instrument(level = "trace", skip(transactions, blobs, proposal_guard))]
1788 async fn new_pending_block(
1789 &self,
1790 transactions: Vec<Transaction>,
1791 blobs: Vec<Blob>,
1792 proposal_guard: &mut Option<PendingProposal>,
1793 ) -> Result<Block, Error> {
1794 let identity = self.identity().await?;
1795
1796 ensure!(
1797 proposal_guard.is_none(),
1798 Error::BlockProposalError(
1799 "Client state already has a pending block; \
1800 use the `linera retry-pending-block` command to commit that first"
1801 )
1802 );
1803 let info = self.chain_info().await?;
1804 let timestamp = self.next_timestamp(&transactions, info.timestamp);
1805 let proposed_block = ProposedBlock {
1806 epoch: info.epoch,
1807 chain_id: self.chain_id,
1808 transactions,
1809 previous_block_hash: info.block_hash,
1810 height: info.next_block_height,
1811 authenticated_owner: Some(identity),
1812 timestamp,
1813 };
1814
1815 let round = self.round_for_oracle(&info, &identity).await?;
1816 let (block, _, never_reject_origins) = self
1819 .client
1820 .stage_block_execution(
1821 proposed_block,
1822 round,
1823 blobs.clone(),
1824 self.options.bundle_execution_policy(),
1825 )
1826 .await?;
1827 if !never_reject_origins.is_empty() {
1830 let skipped = self.skipped_origins.pin();
1831 for origin in never_reject_origins {
1832 skipped.insert(origin);
1833 }
1834 }
1835 let (proposed_block, auto_retry_outcome) = block.clone().into_proposal();
1836 *proposal_guard = Some(PendingProposal {
1837 block: proposed_block,
1838 blobs,
1839 auto_retry_outcome: Some(auto_retry_outcome),
1840 round: None,
1841 });
1842 Ok(block)
1843 }
1844
1845 #[instrument(level = "trace", skip(transactions))]
1850 fn next_timestamp(&self, transactions: &[Transaction], block_time: Timestamp) -> Timestamp {
1851 let local_time = self.storage_client().clock().current_time();
1852 transactions
1853 .iter()
1854 .filter_map(Transaction::incoming_bundle)
1855 .map(|msg| msg.bundle.timestamp)
1856 .max()
1857 .map_or(local_time, |timestamp| timestamp.max(local_time))
1858 .max(block_time)
1859 }
1860
1861 #[instrument(level = "trace", skip(query))]
1863 pub async fn query_application(
1864 &self,
1865 query: Query,
1866 block_hash: Option<CryptoHash>,
1867 ) -> Result<(QueryOutcome, BlockHeight), Error> {
1868 let mut downloaded_blobs = HashSet::<BlobId>::new();
1869 let mut events = super::EventSetDownloader::new(&self.client);
1870 loop {
1871 let result = self
1872 .client
1873 .local_node
1874 .query_application(self.chain_id, query.clone(), block_hash)
1875 .await;
1876 if let Err(LocalNodeError::BlobsNotFound(blob_ids)) = &result {
1877 let new_blobs = super::filter_new(blob_ids, &downloaded_blobs);
1878 if !new_blobs.is_empty() {
1879 let validators = self.client.validator_nodes().await?;
1880 self.client
1881 .update_local_node_with_blobs_from(new_blobs.clone(), &validators)
1882 .await?;
1883 downloaded_blobs.extend(new_blobs);
1884 continue;
1885 }
1886 }
1887 if let Err(LocalNodeError::EventsNotFound(event_ids)) = &result {
1888 if events.download_new(event_ids).await? {
1889 continue;
1890 }
1891 }
1892 return Ok(result?);
1893 }
1894 }
1895
1896 #[cfg(with_testing)]
1898 #[instrument(level = "trace", skip(query))]
1899 pub async fn query_system_application(
1900 &self,
1901 query: linera_execution::SystemQuery,
1902 ) -> Result<QueryOutcome<linera_execution::SystemResponse>, Error> {
1903 let (
1904 QueryOutcome {
1905 response,
1906 operations,
1907 },
1908 _,
1909 ) = self.query_application(Query::System(query), None).await?;
1910 match response {
1911 linera_execution::QueryResponse::System(response) => Ok(QueryOutcome {
1912 response,
1913 operations,
1914 }),
1915 _ => Err(Error::InternalError("Unexpected response for system query")),
1916 }
1917 }
1918
1919 #[instrument(level = "trace", skip(application_id, query))]
1921 #[cfg(with_testing)]
1922 pub async fn query_user_application<A: Abi>(
1923 &self,
1924 application_id: ApplicationId<A>,
1925 query: &A::Query,
1926 ) -> Result<QueryOutcome<A::QueryResponse>, Error> {
1927 let query = Query::user(application_id, query)?;
1928 let (
1929 QueryOutcome {
1930 response,
1931 operations,
1932 },
1933 _,
1934 ) = self.query_application(query, None).await?;
1935 match response {
1936 linera_execution::QueryResponse::User(response_bytes) => {
1937 let response = serde_json::from_slice(&response_bytes)?;
1938 Ok(QueryOutcome {
1939 response,
1940 operations,
1941 })
1942 }
1943 _ => Err(Error::InternalError("Unexpected response for user query")),
1944 }
1945 }
1946
1947 #[instrument(level = "trace")]
1954 pub async fn query_balance(&self) -> Result<Amount, Error> {
1955 let (balance, _) = self.query_balances_with_owner(AccountOwner::CHAIN).await?;
1956 Ok(balance)
1957 }
1958
1959 #[instrument(level = "trace", skip(owner))]
1966 pub async fn query_owner_balance(&self, owner: AccountOwner) -> Result<Amount, Error> {
1967 if owner.is_chain() {
1968 self.query_balance().await
1969 } else {
1970 Ok(self
1971 .query_balances_with_owner(owner)
1972 .await?
1973 .1
1974 .unwrap_or(Amount::ZERO))
1975 }
1976 }
1977
1978 #[instrument(level = "trace", skip(owner))]
1985 pub(crate) async fn query_balances_with_owner(
1986 &self,
1987 owner: AccountOwner,
1988 ) -> Result<(Amount, Option<Amount>), Error> {
1989 let incoming_bundles = self.pending_message_bundles().await?;
1990 if incoming_bundles.is_empty() {
1993 let chain_balance = self.local_balance().await?;
1994 let owner_balance = self.local_owner_balance(owner).await?;
1995 return Ok((chain_balance, Some(owner_balance)));
1996 }
1997 let info = self.chain_info().await?;
1998 let transactions = incoming_bundles
1999 .into_iter()
2000 .map(Transaction::ReceiveMessages)
2001 .collect::<Vec<_>>();
2002 let timestamp = self.next_timestamp(&transactions, info.timestamp);
2003 let block = ProposedBlock {
2004 epoch: info.epoch,
2005 chain_id: self.chain_id,
2006 transactions,
2007 previous_block_hash: info.block_hash,
2008 height: info.next_block_height,
2009 authenticated_owner: if owner == AccountOwner::CHAIN {
2010 None
2011 } else {
2012 Some(owner)
2013 },
2014 timestamp,
2015 };
2016 match self
2017 .client
2018 .stage_block_execution(
2019 block,
2020 None,
2021 Vec::new(),
2022 self.options.bundle_execution_policy(),
2023 )
2024 .await
2025 {
2026 Ok((_, response, _)) => Ok((
2027 response.info.chain_balance,
2028 response.info.requested_owner_balance,
2029 )),
2030 Err(Error::LocalNodeError(LocalNodeError::WorkerError(WorkerError::ChainError(
2031 error,
2032 )))) if matches!(
2033 &*error,
2034 ChainError::ExecutionError(
2035 execution_error,
2036 ChainExecutionContext::Block
2037 ) if matches!(
2038 **execution_error,
2039 ExecutionError::FeesExceedFunding { .. }
2040 )
2041 ) =>
2042 {
2043 Ok((Amount::ZERO, Some(Amount::ZERO)))
2045 }
2046 Err(error) => Err(error),
2047 }
2048 }
2049
2050 #[instrument(level = "trace")]
2054 pub async fn local_balance(&self) -> Result<Amount, Error> {
2055 let (balance, _) = self.local_balances_with_owner(AccountOwner::CHAIN).await?;
2056 Ok(balance)
2057 }
2058
2059 #[instrument(level = "trace", skip(owner))]
2063 pub async fn local_owner_balance(&self, owner: AccountOwner) -> Result<Amount, Error> {
2064 if owner.is_chain() {
2065 self.local_balance().await
2066 } else {
2067 Ok(self
2068 .local_balances_with_owner(owner)
2069 .await?
2070 .1
2071 .unwrap_or(Amount::ZERO))
2072 }
2073 }
2074
2075 #[instrument(level = "trace", skip(owner))]
2079 pub(crate) async fn local_balances_with_owner(
2080 &self,
2081 owner: AccountOwner,
2082 ) -> Result<(Amount, Option<Amount>), Error> {
2083 ensure!(
2084 self.chain_info().await?.next_block_height >= self.initial_next_block_height,
2085 Error::WalletSynchronizationError
2086 );
2087 let mut query = ChainInfoQuery::new(self.chain_id);
2088 query.request_owner_balance = owner;
2089 let response = self
2090 .client
2091 .local_node
2092 .handle_chain_info_query(query)
2093 .await?;
2094 Ok((
2095 response.info.chain_balance,
2096 response.info.requested_owner_balance,
2097 ))
2098 }
2099
2100 #[instrument(level = "trace")]
2102 pub async fn transfer_to_account(
2103 &self,
2104 from: AccountOwner,
2105 amount: Amount,
2106 account: Account,
2107 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2108 self.transfer(from, amount, account).await
2109 }
2110
2111 #[cfg(with_testing)]
2113 #[instrument(level = "trace")]
2114 pub async fn burn(
2115 &self,
2116 owner: AccountOwner,
2117 amount: Amount,
2118 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2119 let recipient = Account::burn_address(self.chain_id);
2120 self.transfer(owner, amount, recipient).await
2121 }
2122
2123 #[instrument(level = "trace")]
2125 pub async fn fetch_chain_info(&self) -> Result<Box<ChainInfo>, Error> {
2126 let validators = self.client.validator_nodes().await?;
2127 self.client
2128 .fetch_chain_info(self.chain_id, &validators)
2129 .await
2130 }
2131
2132 #[instrument(level = "trace")]
2147 pub async fn synchronize_up_to(
2148 &self,
2149 next_height: Option<BlockHeight>,
2150 until_block_time: Option<Timestamp>,
2151 ) -> Result<Box<ChainInfo>, Error> {
2152 let (_, committee) = self.client.admin_committee().await?;
2153 let validators = self.client.make_nodes(&committee)?;
2154 Box::pin(self.client.fetch_chain_info(self.chain_id, &validators)).await?;
2155 communicate_with_quorum(
2156 &validators,
2157 &committee,
2158 |_: &()| (),
2159 |remote_node| async move {
2160 self.client
2161 .download_certificates_from(
2162 &remote_node,
2163 self.chain_id,
2164 next_height.unwrap_or(BlockHeight::MAX),
2165 until_block_time,
2166 )
2167 .await?;
2168 Ok(())
2169 },
2170 self.client.options.quorum_grace_period,
2171 )
2172 .await?;
2173 self.client
2174 .local_node
2175 .chain_info(self.chain_id)
2176 .await
2177 .map_err(Into::into)
2178 }
2179
2180 pub async fn synchronize_from_validators(&self) -> Result<Box<ChainInfo>, Error> {
2182 if self.is_follow_only() {
2183 return self.client.synchronize_chain_state(self.chain_id).await;
2184 }
2185 let info = self.prepare_chain().await?;
2186 self.synchronize_publisher_chains().await?;
2187 self.find_received_certificates().await?;
2188 Ok(info)
2189 }
2190
2191 #[instrument(level = "trace")]
2193 pub async fn process_pending_block(
2194 &self,
2195 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2196 self.prepare_chain().await?;
2197 let mutex = self.proposal_mutex();
2198 let mut proposal_guard = mutex.lock_owned().await;
2199 self.process_pending_block_without_prepare(&mut proposal_guard)
2200 .await
2201 }
2202
2203 #[instrument(level = "debug", skip(self, proposal_guard), fields(chain_id = %self.chain_id))]
2218 async fn process_pending_block_without_prepare(
2219 &self,
2220 proposal_guard: &mut Option<PendingProposal>,
2221 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2222 let mut did_fallback_sync = false;
2224 loop {
2225 let mut snapshot = None;
2230 let err = match self
2231 .process_pending_block_inner(proposal_guard, &mut snapshot)
2232 .await
2233 {
2234 Ok(outcome) => return Ok(outcome),
2235 Err(err) => err,
2236 };
2237 let Some(snapshot) = snapshot else {
2238 return Err(err);
2239 };
2240 let Ok(current) = self.consensus_state_snapshot().await else {
2241 return Err(err);
2242 };
2243 if current == snapshot {
2244 if did_fallback_sync {
2258 return Err(err);
2259 }
2260 did_fallback_sync = true;
2261 if let Err(error) = self.synchronize_chain_state(self.chain_id).await {
2262 tracing::error!(%error, "fallback sync failed after rejected proposal");
2263 return Err(err);
2264 }
2265 let Ok(after_sync) = self.consensus_state_snapshot().await else {
2266 return Err(err);
2267 };
2268 if after_sync == snapshot {
2269 return Err(err);
2270 }
2271 tracing::debug!(
2272 chain_id = %self.chain_id,
2273 ?snapshot,
2274 ?after_sync,
2275 %err,
2276 "fallback sync absorbed new consensus state after rejected proposal; retrying"
2277 );
2278 continue;
2279 }
2280 tracing::debug!(
2281 chain_id = %self.chain_id,
2282 ?snapshot,
2283 ?current,
2284 %err,
2285 "local consensus state advanced during process_pending_block; retrying"
2286 );
2287 }
2288 }
2289
2290 async fn process_pending_block_inner(
2291 &self,
2292 proposal_guard: &mut Option<PendingProposal>,
2293 snapshot: &mut Option<ConsensusStateSnapshot>,
2294 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2295 let process_start = linera_base::time::Instant::now();
2296 tracing::debug!("process_pending_block_without_prepare started");
2297 let info = self.request_leader_timeout_if_needed().await?;
2298 *snapshot = Some(self.consensus_state_snapshot().await?);
2303
2304 if let Some(pending) = &*proposal_guard {
2306 if pending.block.height < info.next_block_height {
2307 tracing::debug!(
2308 "Clearing pending proposal: a block was committed at height {}",
2309 pending.block.height
2310 );
2311 *proposal_guard = None;
2312 }
2313 }
2314
2315 if info.manager.has_locking_block_in_current_round()
2317 && !info.manager.current_round.is_fast()
2318 {
2319 return self.finalize_locking_block(info).await;
2320 }
2321 let identity = self.identity().await?;
2322
2323 let local_node = &self.client.local_node;
2324 let (block, blobs, owner) = if let Some(locking) = &info.manager.requested_locking {
2326 let (block, blobs) = match &**locking {
2327 LockingBlock::Regular(certificate) => {
2328 let blob_ids = certificate.block().required_blob_ids();
2329 let blobs = local_node
2330 .get_locking_blobs(&blob_ids, self.chain_id)
2331 .await?
2332 .ok_or_else(|| Error::InternalError("Missing local locking blobs"))?;
2333 debug!("Retrying locking block from round {}", certificate.round);
2334 (certificate.block().clone(), blobs)
2335 }
2336 LockingBlock::Fast(proposal) => {
2337 let proposed_block = proposal.content.block.clone();
2338 let blob_ids = proposed_block.published_blob_ids();
2339 let blobs = local_node
2340 .get_locking_blobs(&blob_ids, self.chain_id)
2341 .await?
2342 .ok_or_else(|| Error::InternalError("Missing local locking blobs"))?;
2343 let (block, _, _) = self
2344 .client
2345 .stage_block_execution(
2346 proposed_block,
2347 None,
2348 blobs.clone(),
2349 BundleExecutionPolicy::committed(),
2350 )
2351 .await?;
2352 debug!("Retrying locking block from fast round.");
2353 (block, blobs)
2354 }
2355 };
2356 (block, blobs, identity)
2357 } else if let Some(pending) = proposal_guard.as_ref() {
2358 let owner = match pending.block.authenticated_owner {
2365 Some(staged_owner) if staged_owner != identity => {
2366 if !self.has_key_for(&staged_owner).await? {
2367 if pending.round.is_some_and(|round| round.is_fast()) {
2374 return Err(Error::BlockProposalError(
2375 "pending fast block was signed by an owner whose key is no \
2376 longer available; recover the key or wait for the round to \
2377 time out before retrying",
2378 ));
2379 }
2380 warn!(
2381 ?staged_owner, %identity,
2382 "Discarding pending block: no signer key for its authenticated owner",
2383 );
2384 *proposal_guard = None;
2385 return Ok(ClientOutcome::Committed(None));
2386 }
2387 staged_owner
2388 }
2389 _ => identity,
2390 };
2391 let proposed_block = pending.block.clone();
2392 let blobs = pending.blobs.clone();
2393 let staging_outcome = pending.auto_retry_outcome.as_ref();
2394 let round = self.round_for_oracle(&info, &owner).await?;
2395 let (block, _, _) = self
2396 .client
2397 .stage_block_execution(
2398 proposed_block,
2399 round,
2400 blobs.clone(),
2401 BundleExecutionPolicy::committed(),
2402 )
2403 .await?;
2404 if let Some(staging_outcome) = staging_outcome {
2408 ensure!(
2409 block.outcome_matches(staging_outcome),
2410 Error::ExecutionOutcomeMismatch
2411 );
2412 }
2413 debug!("Proposing the local pending block.");
2414 (block, blobs, owner)
2415 } else {
2416 return Ok(ClientOutcome::Committed(None)); };
2418
2419 let has_oracle_responses = block.has_oracle_responses();
2420 let (proposed_block, outcome) = block.into_proposal();
2421 let round = match self
2422 .round_for_new_proposal(&info, &owner, has_oracle_responses)
2423 .await?
2424 {
2425 Either::Left(round) => round,
2426 Either::Right(timeout) => return Ok(ClientOutcome::WaitForTimeout(timeout)),
2427 };
2428 debug!("Proposing block for round {}", round);
2429 if let Some(pending) = proposal_guard.as_mut() {
2430 pending.round.get_or_insert(round);
2431 }
2432
2433 let already_handled_locally = info
2434 .manager
2435 .already_handled_proposal(round, &proposed_block);
2436 let proposal = if let Some(locking) = info.manager.requested_locking {
2438 Box::new(match *locking {
2439 LockingBlock::Regular(cert) => {
2440 BlockProposal::new_retry_regular(owner, round, cert, self.signer())
2441 .await
2442 .map_err(Error::signer_failure)?
2443 }
2444 LockingBlock::Fast(proposal) => {
2445 BlockProposal::new_retry_fast(owner, round, proposal, self.signer())
2446 .await
2447 .map_err(Error::signer_failure)?
2448 }
2449 })
2450 } else {
2451 Box::new(
2452 BlockProposal::new_initial(owner, round, proposed_block.clone(), self.signer())
2453 .await
2454 .map_err(Error::signer_failure)?,
2455 )
2456 };
2457 if !already_handled_locally {
2458 if let Err(err) = local_node.handle_block_proposal(*proposal.clone()).await {
2460 match err {
2461 LocalNodeError::BlobsNotFound(_) => {
2462 local_node
2463 .handle_pending_blobs(self.chain_id, blobs)
2464 .await?;
2465 local_node.handle_block_proposal(*proposal.clone()).await?;
2466 }
2467 err => return Err(err.into()),
2468 }
2469 }
2470 }
2471 *snapshot = Some(self.consensus_state_snapshot().await?);
2476 let committee = self.local_committee().await?;
2477 let block = Block::new(proposed_block, outcome);
2478 let submit_block_proposal_start = linera_base::time::Instant::now();
2480 let certificate = if round.is_fast() {
2481 let hashed_value = ConfirmedBlock::new(block);
2482 self.client
2483 .submit_block_proposal(committee.clone(), proposal, hashed_value)
2484 .await?
2485 } else {
2486 let hashed_value = ValidatedBlock::new(block);
2487 let certificate = self
2488 .client
2489 .submit_block_proposal(committee.clone(), proposal, hashed_value.clone())
2490 .await?;
2491 self.client.finalize_block(&committee, certificate).await?
2492 };
2493 self.send_timing(submit_block_proposal_start, TimingType::SubmitBlockProposal);
2494 tracing::debug!(
2495 total_process_ms = process_start.elapsed().as_millis(),
2496 "process_pending_block_without_prepare completing"
2497 );
2498 debug!(round = %certificate.round, "Sending confirmed block to validators");
2499 let certificate = self.client.storage_client().cache_certificate(certificate);
2500 self.update_validators(Some(&committee), Some(certificate.clone()))
2501 .await?;
2502 *proposal_guard = None;
2504 Ok(ClientOutcome::Committed(Some(CacheArc::unwrap_or_clone(
2505 certificate,
2506 ))))
2507 }
2508
2509 #[expect(
2510 clippy::cast_possible_truncation,
2511 reason = "elapsed millis fits in u64 for any realistic measurement window"
2512 )]
2513 fn send_timing(&self, start: Instant, timing_type: TimingType) {
2514 let Some(sender) = &self.timing_sender else {
2515 return;
2516 };
2517 if let Err(err) = sender.send((start.elapsed().as_millis() as u64, timing_type)) {
2518 tracing::warn!(%err, "Failed to send timing info");
2519 }
2520 }
2521
2522 async fn request_leader_timeout_if_needed(&self) -> Result<Box<ChainInfo>, Error> {
2525 let mut info = self.chain_info_with_manager_values().await?;
2526 if let Some(round_timeout) = info.manager.round_timeout {
2529 if round_timeout <= self.storage_client().clock().current_time() {
2530 if let Err(e) = self.request_leader_timeout().await {
2531 debug!("Failed to obtain a timeout certificate: {}", e);
2532 } else {
2533 info = self.chain_info_with_manager_values().await?;
2534 }
2535 }
2536 }
2537 Ok(info)
2538 }
2539
2540 async fn finalize_locking_block(
2544 &self,
2545 info: Box<ChainInfo>,
2546 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2547 let locking = info
2548 .manager
2549 .requested_locking
2550 .expect("Should have a locking block");
2551 let LockingBlock::Regular(certificate) = *locking else {
2552 panic!("Should have a locking validated block");
2553 };
2554 debug!(
2555 round = %certificate.round,
2556 "Finalizing locking block"
2557 );
2558 let committee = self.local_committee().await?;
2559 let certificate = self
2560 .client
2561 .finalize_block(&committee, certificate.clone())
2562 .await?;
2563 let certificate = self.client.storage_client().cache_certificate(certificate);
2564 self.update_validators(Some(&committee), Some(certificate.clone()))
2565 .await?;
2566 Ok(ClientOutcome::Committed(Some(CacheArc::unwrap_or_clone(
2567 certificate,
2568 ))))
2569 }
2570
2571 async fn round_for_oracle(
2573 &self,
2574 info: &ChainInfo,
2575 identity: &AccountOwner,
2576 ) -> Result<Option<u32>, Error> {
2577 match self.round_for_new_proposal(info, identity, true).await {
2579 Ok(Either::Left(round)) => Ok(round.multi_leader()),
2581 Err(Error::BlockProposalError(_)) | Ok(Either::Right(_)) => Ok(None),
2585 Err(err) => Err(err),
2586 }
2587 }
2588
2589 async fn round_for_new_proposal(
2591 &self,
2592 info: &ChainInfo,
2593 identity: &AccountOwner,
2594 has_oracle_responses: bool,
2595 ) -> Result<Either<Round, RoundTimeout>, Error> {
2596 let manager = &info.manager;
2597 let seed = manager.seed;
2598 let skip_fast = manager.current_round.is_fast()
2603 && (has_oracle_responses || !self.options.allow_fast_blocks);
2604 let conflict = manager
2605 .requested_signed_proposal
2606 .as_ref()
2607 .into_iter()
2608 .chain(&manager.requested_proposed)
2609 .any(|proposal| proposal.content.round == manager.current_round)
2610 || skip_fast;
2611 let round = if !conflict {
2612 manager.current_round
2613 } else if let Some(round) = manager
2614 .ownership
2615 .next_round(manager.current_round)
2616 .filter(|_| manager.current_round.is_multi_leader() || manager.current_round.is_fast())
2617 {
2618 round
2619 } else if let Some(timeout) = info.round_timeout() {
2620 return Ok(Either::Right(timeout));
2621 } else {
2622 return Err(Error::BlockProposalError(
2623 "Conflicting proposal in the current round",
2624 ));
2625 };
2626 let current_committee = self
2627 .local_committee()
2628 .await?
2629 .validators
2630 .values()
2631 .map(|v| (AccountOwner::from(v.account_public_key), v.votes))
2632 .collect();
2633 if manager.should_propose(identity, round, seed, ¤t_committee) {
2634 return Ok(Either::Left(round));
2635 }
2636 if let Some(timeout) = info.round_timeout() {
2637 return Ok(Either::Right(timeout));
2638 }
2639 Err(Error::BlockProposalError(
2640 "Not a leader in the current round",
2641 ))
2642 }
2643
2644 #[instrument(level = "trace")]
2651 pub async fn clear_pending_proposal(&self) {
2652 *self.proposal_mutex().lock().await = None;
2653 }
2654
2655 #[cfg(with_testing)]
2659 #[instrument(level = "trace")]
2660 pub async fn rotate_key_pair(
2661 &self,
2662 public_key: linera_base::crypto::AccountPublicKey,
2663 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2664 self.transfer_ownership(public_key.into()).await
2665 }
2666
2667 #[instrument(level = "trace")]
2669 pub async fn transfer_ownership(
2670 &self,
2671 new_owner: AccountOwner,
2672 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2673 self.execute_operation(SystemOperation::ChangeOwnership {
2674 super_owners: vec![new_owner],
2675 owners: Vec::new(),
2676 first_leader: None,
2677 multi_leader_rounds: 5,
2678 open_multi_leader_rounds: false,
2679 timeout_config: TimeoutConfig::default(),
2680 })
2681 .await
2682 }
2683
2684 #[instrument(level = "trace")]
2686 pub async fn share_ownership(
2687 &self,
2688 new_owner: AccountOwner,
2689 new_weight: u64,
2690 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2691 let ownership = self.prepare_chain().await?.manager.ownership;
2692 ensure!(
2693 ownership.is_active(),
2694 ChainError::InactiveChain(self.chain_id)
2695 );
2696 let mut owners = ownership.owners.into_iter().collect::<Vec<_>>();
2697 owners.extend(ownership.super_owners.into_iter().zip(iter::repeat(100)));
2698 owners.push((new_owner, new_weight));
2699 let operations = vec![Operation::system(SystemOperation::ChangeOwnership {
2700 super_owners: Vec::new(),
2701 owners,
2702 first_leader: ownership.first_leader,
2703 multi_leader_rounds: ownership.multi_leader_rounds,
2704 open_multi_leader_rounds: ownership.open_multi_leader_rounds,
2705 timeout_config: ownership.timeout_config,
2706 })];
2707 self.execute_block(operations, vec![]).await
2708 }
2709
2710 #[instrument(level = "trace")]
2712 pub async fn query_chain_ownership(&self) -> Result<ChainOwnership, Error> {
2713 Ok(self
2714 .client
2715 .local_node
2716 .chain_state_view(self.chain_id)
2717 .await?
2718 .execution_state
2719 .system
2720 .ownership
2721 .get()
2722 .await?
2723 .clone())
2724 }
2725
2726 #[instrument(level = "trace")]
2729 pub async fn change_ownership(
2730 &self,
2731 ownership: ChainOwnership,
2732 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2733 self.execute_operation(SystemOperation::ChangeOwnership {
2734 super_owners: ownership.super_owners.into_iter().collect(),
2735 owners: ownership.owners.into_iter().collect(),
2736 first_leader: ownership.first_leader,
2737 multi_leader_rounds: ownership.multi_leader_rounds,
2738 open_multi_leader_rounds: ownership.open_multi_leader_rounds,
2739 timeout_config: ownership.timeout_config.clone(),
2740 })
2741 .await
2742 }
2743
2744 #[instrument(level = "trace")]
2746 pub async fn query_application_permissions(&self) -> Result<ApplicationPermissions, Error> {
2747 Ok(self
2748 .client
2749 .local_node
2750 .chain_state_view(self.chain_id)
2751 .await?
2752 .execution_state
2753 .system
2754 .application_permissions
2755 .get()
2756 .await?
2757 .clone())
2758 }
2759
2760 #[instrument(level = "trace", skip(application_permissions))]
2762 pub async fn change_application_permissions(
2763 &self,
2764 application_permissions: ApplicationPermissions,
2765 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2766 self.execute_operation(SystemOperation::ChangeApplicationPermissions(
2767 application_permissions,
2768 ))
2769 .await
2770 }
2771
2772 #[instrument(level = "trace", skip(self))]
2774 pub async fn open_chain(
2775 &self,
2776 ownership: ChainOwnership,
2777 application_permissions: ApplicationPermissions,
2778 account: AccountOwner,
2779 balance: Amount,
2780 ) -> Result<ClientOutcome<(ChainDescription, ConfirmedBlockCertificate)>, Error> {
2781 let mut has_key = false;
2783 for owner in ownership.all_owners() {
2784 if self.has_key_for(owner).await? {
2785 has_key = true;
2786 break;
2787 }
2788 }
2789 let config = OpenChainConfig {
2790 ownership,
2791 account,
2792 balance,
2793 application_permissions,
2794 };
2795 let operation = Operation::system(SystemOperation::OpenChain(config));
2796 let certificate = match self.execute_block(vec![operation], vec![]).await? {
2797 ClientOutcome::Committed(certificate) => certificate,
2798 ClientOutcome::Conflict(certificate) => {
2799 return Ok(ClientOutcome::Conflict(certificate));
2800 }
2801 ClientOutcome::WaitForTimeout(timeout) => {
2802 return Ok(ClientOutcome::WaitForTimeout(timeout));
2803 }
2804 };
2805 let chain_blob = certificate
2807 .block()
2808 .body
2809 .blobs
2810 .last()
2811 .and_then(|blobs| blobs.last())
2812 .ok_or_else(|| Error::InternalError("Failed to create a new chain"))?;
2813 let description = bcs::from_bytes::<ChainDescription>(chain_blob.bytes())?;
2814 if has_key {
2816 self.client
2817 .extend_chain_mode(description.id(), ListeningMode::FullChain);
2818 self.client
2819 .retry_pending_cross_chain_requests(self.chain_id)
2820 .await?;
2821 }
2822 Ok(ClientOutcome::Committed((description, certificate)))
2823 }
2824
2825 #[instrument(level = "trace")]
2829 pub async fn checkpoint(&self) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2830 self.execute_operation(SystemOperation::Checkpoint).await
2831 }
2832
2833 #[instrument(level = "trace")]
2836 pub async fn close_chain(
2837 &self,
2838 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2839 match self.execute_operation(SystemOperation::CloseChain).await {
2840 Ok(outcome) => Ok(outcome.map(Some)),
2841 Err(Error::LocalNodeError(LocalNodeError::WorkerError(WorkerError::ChainError(
2842 chain_error,
2843 )))) if matches!(*chain_error, ChainError::ClosedChain) => {
2844 Ok(ClientOutcome::Committed(None)) }
2846 Err(error) => Err(error),
2847 }
2848 }
2849
2850 #[cfg(not(target_arch = "wasm32"))]
2853 #[instrument(level = "trace", skip(contract, service, formats))]
2854 pub async fn publish_module(
2855 &self,
2856 contract: Bytecode,
2857 service: Bytecode,
2858 vm_runtime: VmRuntime,
2859 formats: Option<Vec<u8>>,
2860 ) -> Result<ClientOutcome<(ModuleId, ConfirmedBlockCertificate)>, Error> {
2861 let (blobs, module_id) =
2862 super::create_bytecode_blobs(contract, service, vm_runtime, formats).await;
2863 self.publish_module_blobs(blobs, module_id).await
2864 }
2865
2866 #[cfg(not(target_arch = "wasm32"))]
2868 #[instrument(level = "trace", skip(blobs, module_id))]
2869 pub async fn publish_module_blobs(
2870 &self,
2871 blobs: Vec<Blob>,
2872 module_id: ModuleId,
2873 ) -> Result<ClientOutcome<(ModuleId, ConfirmedBlockCertificate)>, Error> {
2874 self.execute_operations(
2875 vec![Operation::system(SystemOperation::PublishModule {
2876 module_id,
2877 })],
2878 blobs,
2879 )
2880 .await?
2881 .try_map(|certificate| Ok((module_id, certificate)))
2882 }
2883
2884 #[instrument(level = "trace", skip(bytes))]
2886 pub async fn publish_data_blobs(
2887 &self,
2888 bytes: Vec<Vec<u8>>,
2889 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2890 let blobs = bytes.into_iter().map(Blob::new_data);
2891 let publish_blob_operations = blobs
2892 .clone()
2893 .map(|blob| {
2894 Operation::system(SystemOperation::PublishDataBlob {
2895 blob_hash: blob.id().hash,
2896 })
2897 })
2898 .collect();
2899 self.execute_operations(publish_blob_operations, blobs.collect())
2900 .await
2901 }
2902
2903 #[instrument(level = "trace", skip(bytes))]
2905 pub async fn publish_data_blob(
2906 &self,
2907 bytes: Vec<u8>,
2908 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2909 self.publish_data_blobs(vec![bytes]).await
2910 }
2911
2912 #[instrument(
2914 level = "trace",
2915 skip(self, parameters, instantiation_argument, required_application_ids)
2916 )]
2917 pub async fn create_application<
2918 A: Abi,
2919 Parameters: Serialize,
2920 InstantiationArgument: Serialize,
2921 >(
2922 &self,
2923 module_id: ModuleId<A, Parameters, InstantiationArgument>,
2924 parameters: &Parameters,
2925 instantiation_argument: &InstantiationArgument,
2926 required_application_ids: Vec<ApplicationId>,
2927 ) -> Result<ClientOutcome<(ApplicationId<A>, ConfirmedBlockCertificate)>, Error> {
2928 let instantiation_argument = serde_json::to_vec(instantiation_argument)?;
2929 let parameters = serde_json::to_vec(parameters)?;
2930 Ok(self
2931 .create_application_untyped(
2932 module_id.forget_abi(),
2933 parameters,
2934 instantiation_argument,
2935 required_application_ids,
2936 )
2937 .await?
2938 .map(|(app_id, cert)| (app_id.with_abi(), cert)))
2939 }
2940
2941 #[instrument(
2943 level = "trace",
2944 skip(
2945 self,
2946 module_id,
2947 parameters,
2948 instantiation_argument,
2949 required_application_ids
2950 )
2951 )]
2952 pub async fn create_application_untyped(
2953 &self,
2954 module_id: ModuleId,
2955 parameters: Vec<u8>,
2956 instantiation_argument: Vec<u8>,
2957 required_application_ids: Vec<ApplicationId>,
2958 ) -> Result<ClientOutcome<(ApplicationId, ConfirmedBlockCertificate)>, Error> {
2959 self.execute_operation(SystemOperation::CreateApplication {
2960 module_id,
2961 parameters,
2962 instantiation_argument,
2963 required_application_ids,
2964 })
2965 .await?
2966 .try_map(|certificate| {
2967 let mut creation = certificate
2969 .block()
2970 .created_blob_ids()
2971 .into_iter()
2972 .filter(|blob_id| blob_id.blob_type == BlobType::ApplicationDescription)
2973 .collect::<Vec<_>>();
2974 if creation.len() > 1 {
2975 return Err(Error::InternalError(
2976 "Unexpected number of application descriptions published",
2977 ));
2978 }
2979 let blob_id = creation.pop().ok_or(Error::InternalError(
2980 "ApplicationDescription blob not found.",
2981 ))?;
2982 let id = ApplicationId::new(blob_id.hash);
2983 Ok((id, certificate))
2984 })
2985 }
2986
2987 #[instrument(level = "trace", skip(committee))]
2989 pub async fn stage_new_committee(
2990 &self,
2991 committee: Committee,
2992 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2993 let blob = Blob::new(BlobContent::new_committee(bcs::to_bytes(&committee)?));
2994 let blob_hash = blob.id().hash;
2995 match self
2996 .execute_operations(
2997 vec![Operation::system(SystemOperation::Admin(
2998 AdminOperation::PublishCommitteeBlob { blob_hash },
2999 ))],
3000 vec![blob],
3001 )
3002 .await?
3003 {
3004 ClientOutcome::Committed(_) => {}
3005 outcome @ ClientOutcome::WaitForTimeout(_) | outcome @ ClientOutcome::Conflict(_) => {
3006 return Ok(outcome)
3007 }
3008 }
3009 let epoch = self.chain_info().await?.epoch.try_add_one()?;
3010 self.execute_operation(SystemOperation::Admin(AdminOperation::CreateCommittee {
3011 epoch,
3012 blob_hash,
3013 }))
3014 .await
3015 }
3016
3017 #[instrument(level = "trace")]
3023 pub async fn process_inbox(
3024 &self,
3025 ) -> Result<(Vec<ConfirmedBlockCertificate>, Option<RoundTimeout>), Error> {
3026 self.prepare_chain().await?;
3027 self.process_inbox_without_prepare().await
3028 }
3029
3030 #[instrument(level = "trace")]
3036 pub async fn process_inbox_without_prepare(
3037 &self,
3038 ) -> Result<(Vec<ConfirmedBlockCertificate>, Option<RoundTimeout>), Error> {
3039 #[cfg(with_metrics)]
3040 let _latency = super::metrics::PROCESS_INBOX_WITHOUT_PREPARE_LATENCY.measure_latency();
3041
3042 let mut certificates = Vec::new();
3043 loop {
3044 match self.execute_block(vec![], vec![]).await {
3048 Ok(ClientOutcome::Committed(certificate)) => certificates.push(certificate),
3049 Ok(ClientOutcome::Conflict(certificate)) => certificates.push(*certificate),
3050 Ok(ClientOutcome::WaitForTimeout(timeout)) => {
3051 return Ok((certificates, Some(timeout)));
3052 }
3053 Err(Error::LocalNodeError(LocalNodeError::WorkerError(
3055 WorkerError::ChainError(chain_error),
3056 ))) if matches!(*chain_error, ChainError::EmptyBlock) => {
3057 return Ok((certificates, None));
3058 }
3059 Err(error) => return Err(error),
3060 };
3061 }
3062 }
3063
3064 async fn next_epoch_change(&self) -> Result<Option<Operation>, Error> {
3069 let next_epoch = self.chain_info().await?.epoch.try_add_one()?;
3070 if !self
3071 .has_admin_event(EPOCH_STREAM_NAME, next_epoch.0)
3072 .await?
3073 {
3074 return Ok(None);
3075 }
3076 Ok(Some(Operation::system(SystemOperation::ProcessNewEpoch(
3077 next_epoch,
3078 ))))
3079 }
3080
3081 async fn has_admin_event(&self, stream_name: &[u8], index: u32) -> Result<bool, Error> {
3084 let event_id = EventId {
3085 chain_id: self.client.admin_chain_id,
3086 stream_id: StreamId::system(stream_name),
3087 index,
3088 };
3089 Ok(self
3090 .client
3091 .storage_client()
3092 .read_event(event_id)
3093 .await?
3094 .is_some())
3095 }
3096
3097 pub async fn events_from_index(
3099 &self,
3100 stream_id: StreamId,
3101 start_index: u32,
3102 ) -> Result<Vec<IndexAndEvent>, Error> {
3103 Ok(self
3104 .client
3105 .storage_client()
3106 .read_events_from_index(&self.chain_id, &stream_id, start_index)
3107 .await?)
3108 }
3109
3110 #[instrument(level = "trace")]
3114 pub async fn revoke_epochs(
3115 &self,
3116 revoked_epoch: Epoch,
3117 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
3118 self.prepare_chain().await?;
3119 let current_epoch = self.chain_info().await?.epoch;
3120 ensure!(
3121 revoked_epoch < current_epoch,
3122 Error::CannotRevokeCurrentEpoch(current_epoch)
3123 );
3124 let mut operations = Vec::new();
3125 for epoch_index in 0..=revoked_epoch.0 {
3126 let epoch = Epoch(epoch_index);
3127 if self
3128 .has_admin_event(REMOVED_EPOCH_STREAM_NAME, epoch.0)
3129 .await?
3130 {
3131 continue;
3132 }
3133 operations.push(Operation::system(SystemOperation::Admin(
3134 AdminOperation::RemoveCommittee { epoch },
3135 )));
3136 }
3137 ensure!(!operations.is_empty(), Error::EpochAlreadyRevoked);
3138 self.execute_operations(operations, vec![]).await
3139 }
3140
3141 #[cfg(with_testing)]
3145 #[instrument(level = "trace")]
3146 pub async fn transfer_to_account_unsafe_unconfirmed(
3147 &self,
3148 owner: AccountOwner,
3149 amount: Amount,
3150 recipient: Account,
3151 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
3152 self.execute_operation(SystemOperation::Transfer {
3153 owner,
3154 recipient,
3155 amount,
3156 })
3157 .await
3158 }
3159
3160 #[instrument(level = "trace", skip(hash))]
3162 pub async fn read_confirmed_block(
3163 &self,
3164 hash: CryptoHash,
3165 ) -> Result<Arc<ConfirmedBlock>, Error> {
3166 self.client
3167 .storage_client()
3168 .read_confirmed_block(hash)
3169 .await?
3170 .ok_or(Error::MissingConfirmedBlock(hash))
3171 .map(|b| b.into_std())
3172 }
3173
3174 #[instrument(level = "trace", skip(hash))]
3176 pub async fn read_certificate(
3177 &self,
3178 hash: CryptoHash,
3179 ) -> Result<CacheArc<ConfirmedBlockCertificate>, Error> {
3180 self.client
3181 .storage_client()
3182 .read_certificate(hash)
3183 .await?
3184 .ok_or(Error::ReadCertificatesError(vec![hash]))
3185 }
3186
3187 #[instrument(level = "trace")]
3189 pub async fn retry_pending_outgoing_messages(&self) -> Result<(), Error> {
3190 self.client
3191 .retry_pending_cross_chain_requests(self.chain_id)
3192 .await?;
3193 Ok(())
3194 }
3195
3196 #[instrument(level = "trace", skip(local_node))]
3197 async fn maybe_local_chain_info(
3198 &self,
3199 chain_id: ChainId,
3200 local_node: &LocalNodeClient<Env::Storage>,
3201 ) -> Result<Option<Box<ChainInfo>>, Error> {
3202 match local_node.chain_info(chain_id).await {
3203 Ok(info) => Ok(Some(info)),
3204 Err(LocalNodeError::BlobsNotFound(_) | LocalNodeError::InactiveChain(_)) => Ok(None),
3205 Err(err) => Err(err.into()),
3206 }
3207 }
3208
3209 #[instrument(level = "trace", skip(chain_id, local_node))]
3210 async fn local_next_block_height(
3211 &self,
3212 chain_id: ChainId,
3213 local_node: &LocalNodeClient<Env::Storage>,
3214 ) -> Result<BlockHeight, Error> {
3215 Ok(self
3216 .maybe_local_chain_info(chain_id, local_node)
3217 .await?
3218 .map_or(BlockHeight::ZERO, |info| info.next_block_height))
3219 }
3220
3221 #[instrument(level = "trace")]
3224 async fn local_next_height_to_receive(&self, origin: ChainId) -> Result<BlockHeight, Error> {
3225 Ok(self
3226 .client
3227 .local_node
3228 .get_inbox_next_height(self.chain_id, origin)
3229 .await?)
3230 }
3231
3232 #[instrument(level = "trace", skip(remote_node, local_node, notification))]
3233 async fn process_notification(
3234 &self,
3235 remote_node: RemoteNode<Env::ValidatorNode>,
3236 local_node: LocalNodeClient<Env::Storage>,
3237 notification: Notification,
3238 ) -> Result<(), Error> {
3239 let listening_mode = self.client.chain_mode(notification.chain_id);
3240 let relevant = listening_mode
3241 .as_ref()
3242 .is_some_and(|mode| mode.is_relevant(¬ification.reason));
3243 if !relevant {
3244 tracing::trace!(
3245 chain_id = %notification.chain_id,
3246 reason = ?notification.reason,
3247 ?listening_mode,
3248 "Ignoring notification due to listening mode"
3249 );
3250 return Ok(());
3251 }
3252 match notification.reason {
3253 Reason::NewIncomingBundle { origin, height } => {
3254 if self.options.message_policy.ignores_origin(&origin) {
3255 trace!(
3256 chain_id = %self.chain_id,
3257 %origin,
3258 %height,
3259 "Skipping NewIncomingBundle notification: origin filtered by message_policy"
3260 );
3261 return Ok(());
3262 }
3263 if self.local_next_height_to_receive(origin).await? > height {
3264 debug!(
3265 chain_id = %self.chain_id,
3266 "Accepting redundant notification for new message"
3267 );
3268 return Ok(());
3269 }
3270 self.client
3271 .download_sender_block_with_sending_ancestors(
3272 self.chain_id,
3273 origin,
3274 height,
3275 &remote_node,
3276 )
3277 .await?;
3278 if self.local_next_height_to_receive(origin).await? <= height {
3279 info!(
3280 chain_id = %self.chain_id,
3281 "NewIncomingBundle: Fail to synchronize new message after notification"
3282 );
3283 }
3284 }
3285 Reason::NewBlock { height, .. } => {
3286 let chain_id = notification.chain_id;
3287 let local_height = self.local_next_block_height(chain_id, &local_node).await?;
3288 if local_height > height {
3289 debug!(
3290 chain_id = %self.chain_id,
3291 "Accepting redundant notification for new block"
3292 );
3293 return Ok(());
3294 }
3295 self.client
3298 .synchronize_chain_state_from(&remote_node, chain_id)
3299 .await?;
3300 if self.local_next_block_height(chain_id, &local_node).await? <= height {
3301 error!("NewBlock: Fail to synchronize new block after notification");
3302 }
3303 trace!(
3304 chain_id = %self.chain_id,
3305 %height,
3306 "NewBlock: processed notification",
3307 );
3308 }
3309 Reason::NewEvents {
3310 height, block_hash, ..
3311 } => {
3312 let chain_id = notification.chain_id;
3313 let local_height = self.local_next_block_height(chain_id, &local_node).await?;
3314 if local_height > height {
3315 debug!(
3316 chain_id = %self.chain_id,
3317 "Accepting redundant notification for new events"
3318 );
3319 return Ok(());
3320 }
3321 trace!(
3322 chain_id = %self.chain_id,
3323 %height,
3324 "NewEvents: processing notification"
3325 );
3326 let relevant_streams = match self.listening_mode() {
3329 Some(ListeningMode::EventsOnly(subscribed)) => subscribed,
3330 _ => unreachable!(),
3333 };
3334 self.client
3335 .download_event_bearing_blocks(
3336 self.chain_id,
3337 BTreeSet::from([(height, block_hash)]),
3338 local_height,
3339 &relevant_streams,
3340 &remote_node,
3341 )
3342 .await?;
3343 }
3344 Reason::NewRound { height, round } => {
3345 let chain_id = notification.chain_id;
3346 if let Some(info) = self.maybe_local_chain_info(chain_id, &local_node).await? {
3347 if (info.next_block_height, info.manager.current_round) >= (height, round) {
3348 debug!(
3349 chain_id = %self.chain_id,
3350 "Accepting redundant notification for new round"
3351 );
3352 return Ok(());
3353 }
3354 }
3355 self.client
3356 .synchronize_chain_state_from(&remote_node, chain_id)
3357 .await?;
3358 let Some(info) = self.maybe_local_chain_info(chain_id, &local_node).await? else {
3359 error!(
3360 chain_id = %self.chain_id,
3361 "NewRound: Fail to read local chain info for {chain_id}"
3362 );
3363 return Ok(());
3364 };
3365 if (info.next_block_height, info.manager.current_round) < (height, round) {
3366 info!(
3367 chain_id = %self.chain_id,
3368 "NewRound: Fail to synchronize new block after notification"
3369 );
3370 }
3371 }
3372 Reason::BlockExecuted { .. } => {
3373 }
3375 }
3376 Ok(())
3377 }
3378
3379 pub fn is_tracked(&self) -> bool {
3381 self.client.is_tracked(self.chain_id)
3382 }
3383
3384 pub fn listening_mode(&self) -> Option<ListeningMode> {
3386 self.client.chain_mode(self.chain_id)
3387 }
3388
3389 #[instrument(level = "trace", fields(chain_id = ?self.chain_id))]
3394 pub async fn listen(
3395 &self,
3396 ) -> Result<(impl Future<Output = ()>, AbortOnDrop, NotificationStream), Error> {
3397 use future::FutureExt as _;
3398
3399 async fn await_while_polling<F: FusedFuture>(
3400 future: F,
3401 background_work: impl FusedStream<Item = ()>,
3402 ) -> F::Output {
3403 tokio::pin!(future);
3404 tokio::pin!(background_work);
3405 loop {
3406 futures::select! {
3407 _ = background_work.next() => (),
3408 result = future => return result,
3409 }
3410 }
3411 }
3412
3413 let mut senders: HashMap<ValidatorPublicKey, StreamHandle> = HashMap::new();
3414 let mut circuit_breakers: HashMap<ValidatorPublicKey, CircuitBreakerState> = HashMap::new();
3415 let notifications = self.subscribe()?;
3416 let (abortable_notifications, abort) = stream::abortable(self.subscribe()?);
3417
3418 let mut process_notifications = FuturesUnordered::new();
3425
3426 let mut retry_update_at: Option<Timestamp> = None;
3432 match self
3433 .update_notification_streams(&mut senders, &mut circuit_breakers)
3434 .await
3435 {
3436 Ok(tasks) => process_notifications.extend(tasks),
3437 Err(error) => {
3438 error!("Failed to update committee: {error}");
3439 retry_update_at = Some(self.retry_update_deadline());
3440 }
3441 };
3442
3443 let this = self.clone();
3444 let update_streams: MaybeSendBoxFuture<'static, ()> = Box::pin(
3447 async move {
3448 let mut abortable_notifications = abortable_notifications.fuse();
3449 let mut armed_probe_at: Option<Timestamp> = None;
3452 let probe_due = Either::Right(future::pending()).fuse();
3453 tokio::pin!(probe_due);
3454
3455 'listen: loop {
3456 let next_probe_at = circuit_breakers
3461 .values()
3462 .map(|state| state.next_probe_at)
3463 .chain(retry_update_at)
3464 .min();
3465 if next_probe_at != armed_probe_at {
3470 armed_probe_at = next_probe_at;
3471 probe_due.set(
3472 match next_probe_at {
3473 Some(deadline) => Either::Left(
3474 this.storage_client().clock().sleep_until(deadline),
3475 ),
3476 None => Either::Right(future::pending()),
3477 }
3478 .fuse(),
3479 );
3480 }
3481
3482 let update_now = futures::select! {
3483 maybe_notification = abortable_notifications.next() => {
3484 match maybe_notification {
3485 Some(notification) => {
3488 matches!(notification.reason, Reason::NewBlock { .. })
3489 }
3490 None => break 'listen,
3491 }
3492 }
3493 _ = process_notifications.select_next_some() => {
3498 while process_notifications.next().now_or_never().flatten().is_some() {}
3507 true
3508 }
3509 _ = probe_due => {
3515 armed_probe_at = None;
3516 true
3517 }
3518 };
3519 if !update_now {
3520 continue;
3521 }
3522 match Box::pin(await_while_polling(
3526 this.update_notification_streams(&mut senders, &mut circuit_breakers)
3527 .fuse(),
3528 &mut process_notifications,
3529 ))
3530 .await
3531 {
3532 Ok(tasks) => {
3533 retry_update_at = None;
3534 process_notifications.extend(tasks);
3535 }
3536 Err(error) => {
3537 error!("Failed to update committee: {error}");
3538 let retry = this.retry_update_deadline();
3545 this.defer_due_probes(&mut circuit_breakers, retry);
3546 retry_update_at = Some(retry);
3547 }
3548 }
3549 }
3550
3551 for handle in senders.into_values() {
3552 handle.abort.abort();
3553 }
3554
3555 let () = process_notifications.collect().await;
3556 }
3557 .in_current_span(),
3558 );
3559
3560 Ok((update_streams, AbortOnDrop(abort), notifications))
3561 }
3562
3563 fn initial_probe_interval(&self) -> Duration {
3565 self.options
3566 .notification_circuit_breaker_initial_probe_interval
3567 .max(MIN_PROBE_INTERVAL)
3568 }
3569
3570 fn probe_spread(&self, validator: &ValidatorPublicKey) -> Duration {
3577 let mut hasher = DefaultHasher::new();
3578 (self.chain_id, validator).hash(&mut hasher);
3579 (self.initial_probe_interval() / PROBE_SPREAD_DIVISOR)
3580 .mul_f64((hasher.finish() % 1_000) as f64 / 1_000.0)
3581 }
3582
3583 pub(super) fn defer_due_probes(
3591 &self,
3592 circuit_breakers: &mut HashMap<ValidatorPublicKey, CircuitBreakerState>,
3593 retry: Timestamp,
3594 ) {
3595 let now = self.storage_client().clock().current_time();
3596 for state in circuit_breakers.values_mut() {
3597 if state.next_probe_at <= now {
3598 state.next_probe_at = retry;
3599 }
3600 }
3601 }
3602
3603 fn retry_update_deadline(&self) -> Timestamp {
3605 self.storage_client()
3606 .clock()
3607 .current_time()
3608 .saturating_add(TimeDelta::from_duration(self.initial_probe_interval()))
3609 }
3610
3611 #[instrument(level = "trace", skip(senders, circuit_breakers))]
3616 pub(super) async fn update_notification_streams(
3617 &self,
3618 senders: &mut HashMap<ValidatorPublicKey, StreamHandle>,
3619 circuit_breakers: &mut HashMap<ValidatorPublicKey, CircuitBreakerState>,
3620 ) -> Result<Vec<MaybeSendBoxFuture<'static, ()>>, Error> {
3621 let initial_probe_interval = self.initial_probe_interval();
3622 let max_probe_interval = self
3625 .options
3626 .notification_circuit_breaker_max_probe_interval
3627 .max(initial_probe_interval);
3628 let (nodes, local_node) = {
3629 let committee = if self
3633 .listening_mode()
3634 .is_some_and(|m| m.should_sync_chain_state())
3635 {
3636 self.local_committee().await?
3637 } else {
3638 self.client.admin_committee().await?.1
3639 };
3640 let nodes = self
3641 .client
3642 .validator_node_provider()
3643 .make_nodes(&committee)?
3644 .collect::<HashMap<_, _>>();
3645 (nodes, self.client.local_node.clone())
3646 };
3647 let now = self.storage_client().clock().current_time();
3657 let mut abort_stalled_probes = Vec::new();
3664 for (validator, handle) in senders.iter() {
3665 if !nodes.contains_key(validator) {
3666 continue;
3667 }
3668 let died = handle.abort.is_aborted();
3669 let serving_for = handle.serving_for(now);
3670 match (died, serving_for) {
3671 (false, Some(lifetime)) if lifetime >= initial_probe_interval => {
3681 if circuit_breakers
3686 .remove(validator)
3687 .is_some_and(|state| state.tripped)
3688 {
3689 info!(
3690 %validator,
3691 chain_id = %self.chain_id,
3692 "Validator recovered from circuit breaker"
3693 );
3694 }
3695 }
3696 (false, Some(lifetime)) => {
3708 if let Some(state) = circuit_breakers.get_mut(validator) {
3709 if now >= state.next_probe_at {
3710 state.next_probe_at = now.saturating_add(TimeDelta::from_duration(
3711 initial_probe_interval.saturating_sub(lifetime),
3712 ));
3713 }
3714 }
3715 }
3716 (false, None) => {
3719 if let Some(state) = circuit_breakers.get_mut(validator) {
3720 if now >= state.next_probe_at {
3721 if state.tripped {
3725 state.escalate(
3726 now,
3727 max_probe_interval,
3728 self.probe_spread(validator),
3729 );
3730 } else {
3731 *state = CircuitBreakerState::failed(
3732 now,
3733 initial_probe_interval,
3734 self.probe_spread(validator),
3735 );
3736 }
3737 abort_stalled_probes.push(*validator);
3738 warn!(
3739 %validator,
3740 chain_id = %self.chain_id,
3741 next_probe_in = ?state.probe_interval,
3742 "Stream did not start serving in time; aborting and retrying later"
3743 );
3744 }
3745 }
3746 }
3747 (true, Some(lifetime)) if lifetime >= initial_probe_interval => {
3752 circuit_breakers.insert(
3753 *validator,
3754 CircuitBreakerState::churned(
3755 now,
3756 initial_probe_interval,
3757 self.probe_spread(validator),
3758 ),
3759 );
3760 info!(
3761 %validator,
3762 chain_id = %self.chain_id,
3763 ?lifetime,
3764 next_probe_in = ?initial_probe_interval,
3765 "Long-lived validator notification stream ended; re-probing"
3766 );
3767 }
3768 (true, _) => {
3770 if let Some(state) = circuit_breakers
3776 .get_mut(validator)
3777 .filter(|state| state.tripped)
3778 {
3779 state.escalate(now, max_probe_interval, self.probe_spread(validator));
3780 warn!(
3781 %validator,
3782 chain_id = %self.chain_id,
3783 next_probe_in = ?state.probe_interval,
3784 "Validator still unhealthy after probe; increasing probe interval"
3785 );
3786 } else {
3787 circuit_breakers.insert(
3788 *validator,
3789 CircuitBreakerState::failed(
3790 now,
3791 initial_probe_interval,
3792 self.probe_spread(validator),
3793 ),
3794 );
3795 error!(
3796 %validator,
3797 chain_id = %self.chain_id,
3798 next_probe_in = ?initial_probe_interval,
3799 "Validator notification stream ended; entering circuit breaker"
3800 );
3801 }
3802 }
3803 }
3804 }
3805 for validator in abort_stalled_probes {
3806 if let Some(handle) = senders.get(&validator) {
3807 handle.abort.abort();
3808 }
3809 }
3810
3811 senders.retain(|validator, handle| {
3812 if !nodes.contains_key(validator) {
3813 handle.abort.abort();
3814 }
3815 !handle.abort.is_aborted()
3816 });
3817 circuit_breakers.retain(|validator, _| nodes.contains_key(validator));
3818
3819 let mut validator_tasks: Vec<MaybeSendBoxFuture<'static, ()>> = Vec::new();
3820 for (public_key, node) in nodes {
3821 let hash_map::Entry::Vacant(entry) = senders.entry(public_key) else {
3822 continue;
3823 };
3824
3825 let spread = self.probe_spread(&public_key);
3826 match circuit_breakers.entry(public_key) {
3827 hash_map::Entry::Occupied(mut breaker) => {
3828 let state = breaker.get_mut();
3829 if now < state.next_probe_at {
3830 continue;
3831 }
3832 state.arm(now, spread);
3835 debug!(
3836 validator = %public_key,
3837 chain_id = %self.chain_id,
3838 "Probing unhealthy validator"
3839 );
3840 }
3841 hash_map::Entry::Vacant(breaker) => {
3845 breaker.insert(CircuitBreakerState::launched(
3846 now,
3847 initial_probe_interval * FIRST_LAUNCH_GRACE,
3848 spread,
3849 ));
3850 }
3851 }
3852
3853 let address = node.address();
3854 let this = self.clone();
3855 let listening_mode_for_sync = self.listening_mode();
3856 let serving_since = Arc::new(AtomicU64::new(NOT_SERVING));
3857 let serving_since_for_task = serving_since.clone();
3858 let stream = stream::once({
3859 let node = node.clone();
3860 async move {
3861 let stream = node.subscribe(vec![this.chain_id]).await?;
3862 let remote_node = RemoteNode { public_key, node };
3866 if listening_mode_for_sync
3867 .as_ref()
3868 .is_some_and(|mode| mode.should_sync_chain_state())
3869 {
3870 this.client
3871 .synchronize_chain_state_from(&remote_node, this.chain_id)
3872 .await?;
3873 } else {
3874 if let Some(ListeningMode::EventsOnly(subscribed)) =
3878 listening_mode_for_sync.as_ref()
3879 {
3880 if let Err(error) = this
3881 .client
3882 .sync_events_from_node(this.chain_id, subscribed, &remote_node)
3883 .await
3884 {
3885 debug!(
3886 chain_id = %this.chain_id,
3887 %error,
3888 "Failed initial sparse sync for EventsOnly chain"
3889 );
3890 }
3891 }
3892 }
3893 serving_since_for_task.store(
3894 this.storage_client().clock().current_time().micros(),
3895 Ordering::Relaxed,
3896 );
3897 Ok::<_, Error>(stream)
3898 }
3899 })
3900 .filter_map(move |result| {
3901 let address = address.clone();
3902 async move {
3903 if let Err(error) = &result {
3904 info!(?error, address, "could not connect to validator");
3905 } else {
3906 debug!(address, "connected to validator");
3907 }
3908 result.ok()
3909 }
3910 })
3911 .flatten();
3912 let (stream, abort) = stream::abortable(stream);
3913 let mut stream = Box::pin(stream);
3914 let abort_on_exit = abort.clone();
3915 let this = self.clone();
3916 let local_node = local_node.clone();
3917 let remote_node = RemoteNode { public_key, node };
3918 validator_tasks.push(Box::pin(async move {
3919 while let Some(notification) = stream.next().await {
3920 if let Err(error) = this
3921 .process_notification(
3922 remote_node.clone(),
3923 local_node.clone(),
3924 notification.clone(),
3925 )
3926 .await
3927 {
3928 tracing::info!(
3929 chain_id = %this.chain_id,
3930 address = remote_node.address(),
3931 ?notification,
3932 %error,
3933 "failed to process notification",
3934 );
3935 }
3936 }
3937 warn!(
3938 chain_id = %this.chain_id,
3939 address = remote_node.address(),
3940 "Validator notification stream ended"
3941 );
3942 abort_on_exit.abort();
3943 }));
3944 entry.insert(StreamHandle {
3945 abort,
3946 serving_since,
3947 });
3948 }
3949 Ok(validator_tasks)
3950 }
3951
3952 #[instrument(level = "trace", skip(node))]
3958 pub async fn sync_validator(
3959 &self,
3960 public_key: ValidatorPublicKey,
3961 node: Env::ValidatorNode,
3962 ) -> Result<(), Error> {
3963 let local_next_block_height = self.chain_info().await?.next_block_height;
3964 let mut updater = self
3965 .client
3966 .remote_node_updater(RemoteNode { public_key, node });
3967 updater
3968 .send_chain_information(
3969 self.chain_id,
3970 local_next_block_height,
3971 CrossChainMessageDelivery::NonBlocking,
3972 None,
3973 )
3974 .await
3975 }
3976}
3977
3978#[cfg(with_testing)]
3979impl<Env: Environment> ChainClient<Env> {
3980 pub async fn process_notification_from(
3982 &self,
3983 notification: Notification,
3984 validator: (ValidatorPublicKey, &str),
3985 ) {
3986 let mut node_list = self
3987 .client
3988 .validator_node_provider()
3989 .make_nodes_from_list(vec![validator])
3990 .unwrap();
3991 let (public_key, node) = node_list.next().unwrap();
3992 let remote_node = RemoteNode { node, public_key };
3993 let local_node = self.client.local_node.clone();
3994 self.process_notification(remote_node, local_node, notification)
3995 .await
3996 .unwrap();
3997 }
3998}
3999
4000#[cfg(test)]
4001mod tests {
4002 use super::{Error, LocalNodeError};
4003
4004 #[test]
4005 fn error_type_delegates_to_local_node_error() {
4006 assert_eq!(
4007 Error::LocalNodeError(LocalNodeError::InvalidChainInfoResponse).error_type(),
4008 "LocalNodeError::InvalidChainInfoResponse"
4009 );
4010 }
4011
4012 #[test]
4013 fn error_type_falls_back_to_chain_client_variant() {
4014 assert_eq!(
4015 Error::WalletSynchronizationError.error_type(),
4016 "ChainClientError::WalletSynchronizationError"
4017 );
4018 }
4019}