1mod state;
5use std::{
6 collections::{hash_map, BTreeMap, BTreeSet, HashMap, HashSet},
7 convert::Infallible,
8 iter,
9 sync::Arc,
10};
11
12use custom_debug_derive::Debug;
13use futures::{
14 future::{self, Either, FusedFuture, Future},
15 stream::{self, AbortHandle, FusedStream, FuturesUnordered, StreamExt, TryStreamExt},
16};
17#[cfg(with_metrics)]
18use linera_base::prometheus_util::MeasureLatency as _;
19use linera_base::{
20 abi::Abi,
21 crypto::{signer, CryptoHash, Signer, ValidatorPublicKey},
22 data_types::{
23 Amount, ApplicationDescription, ApplicationPermissions, ArithmeticError, Blob, BlobContent,
24 BlockHeight, ChainDescription, Epoch, MessagePolicy, Round, TimeDelta, Timestamp,
25 },
26 ensure,
27 identifiers::{
28 Account, AccountOwner, ApplicationId, BlobId, BlobType, ChainId, EventId, IndexAndEvent,
29 ModuleId, StreamId,
30 },
31 ownership::{ChainOwnership, TimeoutConfig},
32 time::{Duration, Instant},
33};
34#[cfg(not(target_arch = "wasm32"))]
35use linera_base::{data_types::Bytecode, vm::VmRuntime};
36use linera_chain::{
37 data_types::{
38 BlockProposal, BundleExecutionPolicy, BundleFailurePolicy, ChainAndHeight, IncomingBundle,
39 ProposedBlock, Transaction,
40 },
41 manager::LockingBlock,
42 types::{
43 Block, ConfirmedBlock, ConfirmedBlockCertificate, Timeout, TimeoutCertificate,
44 ValidatedBlock,
45 },
46 ChainError, ChainExecutionContext,
47};
48use linera_execution::{
49 committee::Committee,
50 system::{
51 AdminOperation, OpenChainConfig, SystemOperation, EPOCH_STREAM_NAME,
52 REMOVED_EPOCH_STREAM_NAME,
53 },
54 ExecutionError, Operation, Query, QueryOutcome,
55};
56use linera_storage::{Arc as CacheArc, Clock as _, Storage as _};
57use linera_views::ViewError;
58use serde::Serialize;
59pub use state::State;
60use thiserror::Error;
61use tokio::sync::mpsc;
62use tokio_stream::wrappers::UnboundedReceiverStream;
63use tracing::{debug, error, info, instrument, trace, warn, Instrument as _};
64
65use super::{
66 received_log::ReceivedLogs, validator_trackers::ValidatorTrackers, AbortOnDrop, Client,
67 ListeningMode, PendingProposal, TimingType,
68};
69use crate::{
70 data_types::{ChainInfo, ChainInfoQuery, ClientOutcome, RoundTimeout},
71 environment::Environment,
72 local_node::{LocalNodeClient, LocalNodeError},
73 node::{
74 CrossChainMessageDelivery, NodeError, NotificationStream, ValidatorNode,
75 ValidatorNodeProvider as _,
76 },
77 remote_node::RemoteNode,
78 updater::{communicate_with_quorum, CommunicateAction, CommunicationError, ValidatorUpdater},
79 worker::{Notification, Reason, WorkerError},
80};
81
82#[derive(Debug, Clone)]
84pub struct Options {
85 pub max_pending_message_bundles: usize,
87 pub max_block_limit_errors: u32,
92 pub staging_bundles_time_budget: Option<Duration>,
95 pub message_policy: MessagePolicy,
97 pub priority_bundle_origins: HashSet<ChainId>,
99 pub cross_chain_message_delivery: CrossChainMessageDelivery,
101 pub quorum_grace_period: f64,
104 pub blob_download_hedge_delay: Duration,
106 pub certificate_batch_download_hedge_delay: Duration,
108 pub certificate_download_batch_size: u64,
111 pub certificate_upload_batch_size: usize,
114 pub sender_certificate_download_batch_size: usize,
117 pub max_concurrent_batch_downloads: usize,
119 pub max_joined_tasks: usize,
121 pub allow_fast_blocks: bool,
124 pub notification_circuit_breaker_initial_probe_interval: Duration,
128 pub notification_circuit_breaker_max_probe_interval: Duration,
131 pub max_event_stream_queries: usize,
134}
135
136struct CircuitBreakerState {
137 next_probe_at: Timestamp,
138 probe_interval: Duration,
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
144struct ConsensusStateSnapshot {
145 next_block_height: BlockHeight,
146 current_round: Round,
147 lock_round: Option<Round>,
148 timeout_round: Option<Round>,
149}
150
151#[cfg(with_testing)]
152impl Options {
153 pub fn test_default() -> Self {
155 use super::{
156 DEFAULT_CERTIFICATE_DOWNLOAD_BATCH_SIZE, DEFAULT_CERTIFICATE_UPLOAD_BATCH_SIZE,
157 DEFAULT_MAX_CONCURRENT_BATCH_DOWNLOADS, DEFAULT_MAX_EVENT_STREAM_QUERIES,
158 DEFAULT_SENDER_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
159 };
160 use crate::DEFAULT_QUORUM_GRACE_PERIOD;
161
162 Options {
163 max_pending_message_bundles: 10,
164 max_block_limit_errors: 3,
165 staging_bundles_time_budget: None,
166 message_policy: MessagePolicy::default(),
167 priority_bundle_origins: HashSet::new(),
168 cross_chain_message_delivery: CrossChainMessageDelivery::NonBlocking,
169 quorum_grace_period: DEFAULT_QUORUM_GRACE_PERIOD,
170 blob_download_hedge_delay: Duration::from_secs(1),
171 certificate_batch_download_hedge_delay: Duration::from_secs(1),
172 certificate_download_batch_size: DEFAULT_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
173 certificate_upload_batch_size: DEFAULT_CERTIFICATE_UPLOAD_BATCH_SIZE,
174 sender_certificate_download_batch_size: DEFAULT_SENDER_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
175 max_concurrent_batch_downloads: DEFAULT_MAX_CONCURRENT_BATCH_DOWNLOADS,
176 max_joined_tasks: 100,
177 allow_fast_blocks: false,
178 notification_circuit_breaker_initial_probe_interval: Duration::from_secs(300),
179 notification_circuit_breaker_max_probe_interval: Duration::from_secs(3600),
180 max_event_stream_queries: DEFAULT_MAX_EVENT_STREAM_QUERIES,
181 }
182 }
183}
184
185impl Options {
186 pub fn bundle_execution_policy(&self) -> BundleExecutionPolicy {
188 BundleExecutionPolicy {
189 on_failure: BundleFailurePolicy::AutoRetry {
190 max_failures: self.max_block_limit_errors,
191 never_reject_application_ids: Arc::new(
192 self.message_policy.never_reject_application_ids.clone(),
193 ),
194 },
195 time_budget: self.staging_bundles_time_budget,
196 }
197 }
198}
199
200#[derive(Debug)]
206pub struct ChainClient<Env: Environment> {
207 #[debug(skip)]
209 pub(crate) client: Arc<Client<Env>>,
210 chain_id: ChainId,
212 #[debug(skip)]
214 options: Options,
215 preferred_owner: Option<AccountOwner>,
218 initial_next_block_height: BlockHeight,
220 initial_block_hash: Option<CryptoHash>,
222 timing_sender: Option<mpsc::UnboundedSender<(u64, TimingType)>>,
224 skipped_origins: Arc<papaya::HashSet<ChainId>>,
227}
228
229impl<Env: Environment> Clone for ChainClient<Env> {
230 fn clone(&self) -> Self {
231 Self {
232 client: self.client.clone(),
233 chain_id: self.chain_id,
234 options: self.options.clone(),
235 preferred_owner: self.preferred_owner,
236 initial_next_block_height: self.initial_next_block_height,
237 initial_block_hash: self.initial_block_hash,
238 timing_sender: self.timing_sender.clone(),
239 skipped_origins: self.skipped_origins.clone(),
240 }
241 }
242}
243
244#[derive(Debug, Error, strum::IntoStaticStr)]
246#[allow(missing_docs)]
247pub enum Error {
248 #[error("Local node operation failed: {0}")]
249 LocalNodeError(#[from] LocalNodeError),
250
251 #[error("Remote node operation failed: {0}")]
252 RemoteNodeError(#[from] NodeError),
253
254 #[error(transparent)]
255 ArithmeticError(#[from] ArithmeticError),
256
257 #[error("Missing certificates: {0:?}")]
258 ReadCertificatesError(Vec<CryptoHash>),
259
260 #[error("Missing confirmed block: {0:?}")]
261 MissingConfirmedBlock(CryptoHash),
262
263 #[error("JSON (de)serialization error: {0}")]
264 JsonError(#[from] serde_json::Error),
265
266 #[error("Chain operation failed: {0}")]
267 ChainError(#[from] ChainError),
268
269 #[error(transparent)]
270 CommunicationError(#[from] CommunicationError<NodeError>),
271
272 #[error("Internal error within chain client: {0}")]
273 InternalError(&'static str),
274
275 #[error(
276 "Cannot accept a certificate from an unknown committee in the future. \
277 Please synchronize the local view of the admin chain"
278 )]
279 CommitteeSynchronizationError,
280
281 #[error("The local node is behind the trusted state in wallet and needs synchronization with validators")]
282 WalletSynchronizationError,
283
284 #[error("The state of the client is incompatible with the proposed block: {0}")]
285 BlockProposalError(&'static str),
286
287 #[error(
288 "Cannot accept a certificate from a committee that was retired. \
289 Try a newer certificate from the same origin"
290 )]
291 CommitteeDeprecationError,
292
293 #[error("Protocol error within chain client: {0}")]
294 ProtocolError(&'static str),
295
296 #[error("Signer doesn't have key to sign for chain {0}")]
297 CannotFindKeyForChain(ChainId),
298
299 #[error("client is not configured to propose on chain {0}")]
300 NoAccountKeyConfigured(ChainId),
301
302 #[error("The chain client isn't owner on chain {0}")]
303 NotAnOwner(ChainId),
304
305 #[error(transparent)]
306 ViewError(#[from] ViewError),
307
308 #[error(
309 "Failed to download certificates and update local node to the next height \
310 {target_next_block_height} of chain {chain_id}"
311 )]
312 CannotDownloadCertificates {
313 chain_id: ChainId,
314 target_next_block_height: BlockHeight,
315 },
316
317 #[error("No validator provided a usable certificate registering blob {0}")]
318 CannotDownloadBlob(BlobId),
319
320 #[error(transparent)]
321 BcsError(#[from] bcs::Error),
322
323 #[error(
324 "Unexpected quorum: validators voted for block hash {hash} in {round}, \
325 expected block hash {expected_hash} in {expected_round}"
326 )]
327 UnexpectedQuorum {
328 hash: CryptoHash,
329 round: Round,
330 expected_hash: CryptoHash,
331 expected_round: Round,
332 },
333
334 #[error("signer error: {0:?}")]
335 Signer(#[source] Box<dyn signer::Error>),
336
337 #[error("Cannot revoke the current epoch {0}")]
338 CannotRevokeCurrentEpoch(Epoch),
339
340 #[error("Epoch is already revoked")]
341 EpochAlreadyRevoked,
342
343 #[error("Failed to download missing sender blocks from chain {chain_id} at height {height}")]
344 CannotDownloadMissingSenderBlock {
345 chain_id: ChainId,
346 height: BlockHeight,
347 },
348
349 #[error(
350 "A different block was already committed at this height. \
351 The committed certificate hash is {0}"
352 )]
353 Conflict(CryptoHash),
354
355 #[error(
356 "Execution outcome mismatch: AutoRetry and committed execution produced \
357 different outcomes for the same block"
358 )]
359 ExecutionOutcomeMismatch,
360}
361
362impl From<Infallible> for Error {
363 fn from(infallible: Infallible) -> Self {
364 match infallible {}
365 }
366}
367
368impl Error {
369 pub fn signer_failure(err: impl signer::Error + 'static) -> Self {
371 Self::Signer(Box::new(err))
372 }
373
374 pub fn error_type(&self) -> String {
378 match self {
379 Error::LocalNodeError(local_node_error) => local_node_error.error_type(),
380 Error::ChainError(chain_error) => chain_error.error_type(),
381 other => {
382 let variant: &'static str = other.into();
383 format!("ChainClientError::{variant}")
384 }
385 }
386 }
387}
388
389impl<Env: Environment> ChainClient<Env> {
390 pub fn new(
392 client: Arc<Client<Env>>,
393 chain_id: ChainId,
394 options: Options,
395 initial_block_hash: Option<CryptoHash>,
396 initial_next_block_height: BlockHeight,
397 preferred_owner: Option<AccountOwner>,
398 timing_sender: Option<mpsc::UnboundedSender<(u64, TimingType)>>,
399 ) -> Self {
400 ChainClient {
401 client,
402 chain_id,
403 options,
404 preferred_owner,
405 initial_block_hash,
406 initial_next_block_height,
407 timing_sender,
408 skipped_origins: Arc::new(papaya::HashSet::new()),
409 }
410 }
411
412 pub fn is_follow_only(&self) -> bool {
414 self.client.is_chain_follow_only(self.chain_id)
415 }
416
417 #[instrument(level = "trace", skip(self))]
421 fn proposal_mutex(&self) -> Arc<tokio::sync::Mutex<Option<PendingProposal>>> {
422 self.client
423 .chains
424 .pin()
425 .get(&self.chain_id)
426 .expect("Chain client constructed for invalid chain")
427 .proposal_mutex()
428 }
429
430 #[instrument(level = "trace", skip(self))]
432 pub async fn pending_proposal(&self) -> Option<PendingProposal> {
433 self.proposal_mutex().lock().await.clone()
434 }
435
436 #[instrument(level = "trace", skip(self))]
438 pub fn signer(&self) -> &impl Signer {
439 self.client.signer()
440 }
441
442 pub async fn has_key_for(&self, owner: &AccountOwner) -> Result<bool, Error> {
444 self.client.has_key_for(owner).await
445 }
446
447 #[instrument(level = "trace", skip(self))]
449 pub fn options_mut(&mut self) -> &mut Options {
450 &mut self.options
451 }
452
453 #[instrument(level = "trace", skip(self))]
455 pub fn options(&self) -> &Options {
456 &self.options
457 }
458
459 #[instrument(level = "trace", skip(self))]
461 pub fn chain_id(&self) -> ChainId {
462 self.chain_id
463 }
464
465 pub fn timing_sender(&self) -> Option<mpsc::UnboundedSender<(u64, TimingType)>> {
467 self.timing_sender.clone()
468 }
469
470 #[instrument(level = "trace", skip(self))]
472 pub fn admin_chain_id(&self) -> ChainId {
473 self.client.admin_chain_id
474 }
475
476 #[instrument(level = "trace", skip(self))]
478 pub fn preferred_owner(&self) -> Option<AccountOwner> {
479 self.preferred_owner
480 }
481
482 #[instrument(level = "trace", skip(self))]
484 pub fn set_preferred_owner(&mut self, preferred_owner: AccountOwner) {
485 self.preferred_owner = Some(preferred_owner);
486 }
487
488 #[instrument(level = "trace")]
490 pub async fn chain_state_view(
491 &self,
492 ) -> Result<crate::worker::ChainStateViewReadGuard<Env::Storage>, LocalNodeError> {
493 self.client.local_node.chain_state_view(self.chain_id).await
494 }
495
496 #[instrument(level = "trace", skip(self))]
498 pub async fn event_stream_publishers(
499 &self,
500 ) -> Result<BTreeMap<ChainId, BTreeSet<StreamId>>, LocalNodeError> {
501 let subscriptions = self
502 .client
503 .local_node
504 .get_event_subscriptions(self.chain_id)
505 .await?;
506 let mut publishers = subscriptions.into_iter().fold(
507 BTreeMap::<ChainId, BTreeSet<StreamId>>::new(),
508 |mut map, ((chain_id, stream_id), _)| {
509 map.entry(chain_id).or_default().insert(stream_id);
510 map
511 },
512 );
513 if self.chain_id != self.client.admin_chain_id {
514 publishers.insert(
515 self.client.admin_chain_id,
516 vec![
517 StreamId::system(EPOCH_STREAM_NAME),
518 StreamId::system(REMOVED_EPOCH_STREAM_NAME),
519 ]
520 .into_iter()
521 .collect(),
522 );
523 }
524 Ok(publishers)
525 }
526
527 #[instrument(level = "trace")]
529 pub fn subscribe(&self) -> Result<NotificationStream, LocalNodeError> {
530 self.subscribe_to(self.chain_id)
531 }
532
533 #[instrument(level = "trace")]
535 pub fn subscribe_to(&self, chain_id: ChainId) -> Result<NotificationStream, LocalNodeError> {
536 Ok(Box::pin(UnboundedReceiverStream::new(
537 self.client.notifier.subscribe(vec![chain_id]),
538 )))
539 }
540
541 #[instrument(level = "trace")]
543 pub fn storage_client(&self) -> &Env::Storage {
544 self.client.storage_client()
545 }
546
547 #[instrument(level = "trace")]
549 pub async fn chain_info(&self) -> Result<Box<ChainInfo>, LocalNodeError> {
550 let query = ChainInfoQuery::new(self.chain_id);
551 let response = self
552 .client
553 .local_node
554 .handle_chain_info_query(query)
555 .await?;
556 Ok(response.info)
557 }
558
559 #[instrument(level = "trace")]
561 pub async fn chain_info_with_manager_values(&self) -> Result<Box<ChainInfo>, LocalNodeError> {
562 let query = ChainInfoQuery::new(self.chain_id).with_manager_values();
563 let response = self
564 .client
565 .local_node
566 .handle_chain_info_query(query)
567 .await?;
568 Ok(response.info)
569 }
570
571 async fn consensus_state_snapshot(&self) -> Result<ConsensusStateSnapshot, Error> {
580 let info = self.chain_info_with_manager_values().await?;
581 let lock_round = info
582 .manager
583 .requested_locking
584 .as_deref()
585 .map(LockingBlock::round);
586 let timeout_round = info.manager.timeout.as_deref().map(|cert| cert.round);
587 Ok(ConsensusStateSnapshot {
588 next_block_height: info.next_block_height,
589 current_round: info.manager.current_round,
590 lock_round,
591 timeout_round,
592 })
593 }
594
595 pub async fn get_chain_description(&self) -> Result<ChainDescription, Error> {
597 self.client.get_chain_description(self.chain_id).await
598 }
599
600 pub async fn get_application_description(
604 &self,
605 application_id: ApplicationId,
606 ) -> Result<ApplicationDescription, Error> {
607 self.client
608 .get_application_description(application_id)
609 .await
610 }
611
612 #[instrument(level = "trace")]
615 async fn pending_message_bundles(&self) -> Result<Vec<IncomingBundle>, Error> {
616 if self.options.message_policy.is_ignore() {
617 return Ok(Vec::new());
619 }
620
621 let query = ChainInfoQuery::new(self.chain_id).with_pending_message_bundles();
622 let info = self
623 .client
624 .local_node
625 .handle_chain_info_query(query)
626 .await?
627 .info;
628 if self.preferred_owner.is_some_and(|owner| {
629 info.manager
630 .ownership
631 .is_super_owner_no_regular_owners(&owner)
632 }) {
633 ensure!(
635 info.next_block_height >= self.initial_next_block_height,
636 Error::WalletSynchronizationError
637 );
638 }
639
640 let skipped = self.skipped_origins.pin();
641 let mut bundles = info
642 .requested_pending_message_bundles
643 .into_iter()
644 .filter_map(|bundle| bundle.apply_policy(&self.options.message_policy))
645 .filter(|bundle| !skipped.contains(&bundle.origin))
646 .collect::<Vec<_>>();
647 let priority_origins = &self.options.priority_bundle_origins;
648 bundles.sort_by(|a, b| {
649 let a_priority = priority_origins.contains(&a.origin);
650 let b_priority = priority_origins.contains(&b.origin);
651 b_priority
652 .cmp(&a_priority)
653 .then(a.bundle.timestamp.cmp(&b.bundle.timestamp))
654 });
655 bundles.truncate(self.options.max_pending_message_bundles);
656 Ok(bundles)
657 }
658
659 #[instrument(level = "trace")]
660 async fn collect_stream_updates(&self) -> Result<Vec<Operation>, Error> {
661 let subscription_map = self
662 .client
663 .local_node
664 .get_event_subscriptions(self.chain_id)
665 .await?;
666 let futures = subscription_map
667 .into_iter()
668 .filter(|((chain_id, _), _)| {
669 self.options
670 .message_policy
671 .restrict_chain_ids_to
672 .as_ref()
673 .is_none_or(|chain_set| chain_set.contains(chain_id))
674 })
675 .filter(|((_, stream_id), _)| {
676 self.options
677 .message_policy
678 .process_events_from_application_ids
679 .as_ref()
680 .is_none_or(|app_set| app_set.contains(&stream_id.application_id))
681 })
682 .map(|((chain_id, stream_id), subscriptions)| {
683 let client = self.client.clone();
684 async move {
685 let counts = client
686 .local_node
687 .get_stream_indices(chain_id, stream_id.clone())
688 .await?;
689 let (first_index, next_index) = (counts.first_index, counts.next_index);
690 if next_index <= subscriptions.min_next_index {
691 return Ok::<_, Error>(Vec::new());
692 }
693 Ok(subscriptions
694 .applications
695 .into_iter()
696 .filter(|(_, app_index)| *app_index < next_index)
697 .map(|(application_id, _)| {
698 SystemOperation::UpdateStream {
699 application_id,
700 chain_id,
701 stream_id: stream_id.clone(),
702 first_index,
703 next_index,
704 }
705 .into()
706 })
707 .collect::<Vec<Operation>>())
708 }
709 });
710 Ok(futures::stream::iter(futures)
711 .buffer_unordered(self.options.max_joined_tasks)
712 .try_collect::<Vec<_>>()
713 .await?
714 .into_iter()
715 .flatten()
716 .collect::<Vec<_>>())
717 }
718
719 #[instrument(level = "trace")]
721 pub async fn local_committee(&self) -> Result<Arc<Committee>, Error> {
722 let info = match self.client.local_node.chain_info(self.chain_id).await {
723 Ok(info) => info,
724 Err(LocalNodeError::BlobsNotFound(_)) => {
725 self.synchronize_chain_state(self.chain_id).await?;
726 self.client.local_node.chain_info(self.chain_id).await?
727 }
728 Err(err) => return Err(err.into()),
729 };
730 let hash = info
731 .committee_hash
732 .ok_or(LocalNodeError::InactiveChain(self.chain_id))?;
733 Ok(self
734 .storage_client()
735 .get_or_load_committee_by_hash(hash)
736 .await
737 .map_err(LocalNodeError::from)?)
738 }
739
740 #[instrument(level = "trace")]
742 pub async fn admin_committee(&self) -> Result<(Epoch, Arc<Committee>), LocalNodeError> {
743 self.client.admin_committee().await
744 }
745
746 #[instrument(level = "trace")]
750 pub async fn identity(&self) -> Result<AccountOwner, Error> {
751 let Some(preferred_owner) = self.preferred_owner else {
752 return Err(Error::NoAccountKeyConfigured(self.chain_id));
753 };
754 let manager = self.chain_info().await?.manager;
755 ensure!(
756 manager.ownership.is_active(),
757 LocalNodeError::InactiveChain(self.chain_id)
758 );
759 let fallback_owners = if manager.ownership.has_fallback() {
760 self.local_committee()
761 .await?
762 .account_keys_and_weights()
763 .map(|(key, _)| AccountOwner::from(key))
764 .collect()
765 } else {
766 BTreeSet::new()
767 };
768
769 let is_owner = manager
770 .ownership
771 .can_propose_in_multi_leader_round(&preferred_owner)
772 || fallback_owners.contains(&preferred_owner);
773
774 if !is_owner {
775 warn!(
776 chain_id = %self.chain_id,
777 ownership = ?manager.ownership,
778 ?fallback_owners,
779 ?preferred_owner,
780 "The preferred owner is not configured as an owner of this chain",
781 );
782 return Err(Error::NotAnOwner(self.chain_id));
783 }
784
785 let has_signer = self.has_key_for(&preferred_owner).await?;
786
787 if !has_signer {
788 warn!(%self.chain_id, ?preferred_owner,
789 "Chain is one of the owners but its Signer instance doesn't contain the key",
790 );
791 return Err(Error::CannotFindKeyForChain(self.chain_id));
792 }
793
794 Ok(preferred_owner)
795 }
796
797 #[instrument(level = "trace")]
805 pub async fn prepare_for_owner(&self, owner: AccountOwner) -> Result<Box<ChainInfo>, Error> {
806 ensure!(
807 self.has_key_for(&owner).await?,
808 Error::CannotFindKeyForChain(self.chain_id)
809 );
810 self.client
812 .get_chain_description_blob(self.chain_id)
813 .await?;
814
815 let info = self.chain_info().await?;
817
818 ensure!(
820 info.manager
821 .ownership
822 .can_propose_in_multi_leader_round(&owner),
823 Error::NotAnOwner(self.chain_id)
824 );
825
826 Ok(info)
827 }
828
829 #[instrument(level = "trace")]
832 pub async fn prepare_chain(&self) -> Result<Box<ChainInfo>, Error> {
833 #[cfg(with_metrics)]
834 let _latency = super::metrics::PREPARE_CHAIN_LATENCY.measure_latency();
835
836 let mut info = self.synchronize_to_known_height().await?;
837
838 if self.preferred_owner.is_none_or(|owner| {
839 !info
840 .manager
841 .ownership
842 .is_super_owner_no_regular_owners(&owner)
843 }) {
844 info = self.client.synchronize_chain_state(self.chain_id).await?;
848 }
849
850 if info.epoch > self.client.admin_committee().await?.0 {
851 self.client
852 .synchronize_chain_state(self.client.admin_chain_id)
853 .await?;
854 }
855
856 Ok(info)
857 }
858
859 async fn synchronize_to_known_height(&self) -> Result<Box<ChainInfo>, Error> {
864 let info = self
865 .client
866 .download_certificates(self.chain_id, self.initial_next_block_height)
867 .await?;
868 if info.next_block_height == self.initial_next_block_height {
869 ensure!(
871 self.initial_block_hash == info.block_hash,
872 Error::InternalError("Invalid chain of blocks in local node")
873 );
874 }
875 Ok(info)
876 }
877
878 #[instrument(level = "trace", skip(old_committee, latest_certificate))]
880 pub async fn update_validators(
881 &self,
882 old_committee: Option<&Committee>,
883 latest_certificate: Option<CacheArc<ConfirmedBlockCertificate>>,
884 ) -> Result<(), Error> {
885 let update_validators_start = linera_base::time::Instant::now();
886 if let Some(old_committee) = old_committee {
888 self.communicate_chain_updates(old_committee, latest_certificate.clone())
889 .await?
890 };
891 if let Ok(new_committee) = self.local_committee().await {
892 if old_committee.is_none_or(|old| *new_committee != *old) {
893 self.communicate_chain_updates(&new_committee, latest_certificate)
896 .await?;
897 }
898 }
899 self.send_timing(update_validators_start, TimingType::UpdateValidators);
900 Ok(())
901 }
902
903 #[instrument(level = "trace", skip(committee))]
905 pub async fn communicate_chain_updates(
906 &self,
907 committee: &Committee,
908 latest_certificate: Option<CacheArc<ConfirmedBlockCertificate>>,
909 ) -> Result<(), Error> {
910 let delivery = self.options.cross_chain_message_delivery;
911 let height = self.chain_info().await?.next_block_height;
912 self.client
913 .communicate_chain_updates(
914 committee,
915 self.chain_id,
916 height,
917 delivery,
918 latest_certificate,
919 )
920 .await
921 }
922
923 async fn synchronize_publisher_chains(&self) -> Result<(), Error> {
927 let subscriptions = self
928 .client
929 .local_node
930 .get_event_subscriptions(self.chain_id)
931 .await?;
932 let mut streams_by_chain = BTreeMap::<ChainId, BTreeSet<StreamId>>::new();
934 for ((chain_id, stream_id), _) in &subscriptions {
935 if *chain_id != self.chain_id {
936 streams_by_chain
937 .entry(*chain_id)
938 .or_default()
939 .insert(stream_id.clone());
940 }
941 }
942 let admin_chain_id = self.client.admin_chain_id;
944 if admin_chain_id != self.chain_id {
945 self.client.synchronize_chain_state(admin_chain_id).await?;
946 }
947 let (_, committee) = self.admin_committee().await?;
949 let nodes = self.client.make_nodes(&committee)?;
950 let tasks = streams_by_chain
951 .into_iter()
952 .filter(|(chain_id, _)| *chain_id != admin_chain_id)
953 .map(|(chain_id, stream_ids)| {
954 self.sync_publisher_chain_events(chain_id, stream_ids, &nodes, &committee)
955 })
956 .collect::<Vec<_>>();
957 stream::iter(tasks)
958 .buffer_unordered(self.options.max_joined_tasks)
959 .collect::<Vec<_>>()
960 .await
961 .into_iter()
962 .collect::<Result<Vec<_>, _>>()?;
963 Ok(())
964 }
965
966 async fn sync_publisher_chain_events(
973 &self,
974 publisher_chain_id: ChainId,
975 stream_ids: BTreeSet<StreamId>,
976 nodes: &[RemoteNode<Env::ValidatorNode>],
977 committee: &Committee,
978 ) -> Result<(), Error> {
979 let stream_ids_ref = &stream_ids;
980 communicate_with_quorum(
981 nodes,
982 committee,
983 |_: &()| (),
984 |remote_node| async move {
985 self.client
986 .sync_events_from_node(publisher_chain_id, stream_ids_ref, &remote_node)
987 .await
988 },
989 self.options.quorum_grace_period,
990 )
991 .await?;
992 Ok(())
993 }
994
995 #[instrument(level = "debug", skip(self), fields(chain_id = %self.chain_id))]
1004 pub async fn find_received_certificates(&self) -> Result<(), Error> {
1005 debug!("starting find_received_certificates");
1006 #[cfg(with_metrics)]
1007 let _latency = super::metrics::FIND_RECEIVED_CERTIFICATES_LATENCY.measure_latency();
1008 let chain_id = self.chain_id;
1010 let (_, committee) = self.admin_committee().await?;
1011 let nodes = self.client.make_nodes(&committee)?;
1012
1013 let trackers = self
1014 .client
1015 .local_node
1016 .get_received_certificate_trackers(chain_id)
1017 .await?;
1018
1019 trace!("find_received_certificates: read trackers");
1020
1021 let received_log_batches = Arc::new(std::sync::Mutex::new(Vec::new()));
1022 let result = communicate_with_quorum(
1024 &nodes,
1025 &committee,
1026 |_| (),
1027 |remote_node| {
1028 let client = &self.client;
1029 let tracker = trackers.get(&remote_node.public_key).copied().unwrap_or(0);
1030 let received_log_batches = Arc::clone(&received_log_batches);
1031 Box::pin(async move {
1032 let batch = client
1033 .get_received_log_from_validator(chain_id, &remote_node, tracker)
1034 .await?;
1035 let mut batches = received_log_batches.lock().unwrap();
1036 batches.push((remote_node.public_key, batch));
1037 Ok(())
1038 })
1039 },
1040 self.options.quorum_grace_period,
1041 )
1042 .await;
1043
1044 if let Err(error) = result {
1045 error!(
1046 %error,
1047 "Failed to synchronize received_logs from at least a quorum of validators",
1048 );
1049 }
1050
1051 let received_logs: Vec<_> = {
1052 let mut received_log_batches = received_log_batches.lock().unwrap();
1053 std::mem::take(received_log_batches.as_mut())
1054 };
1055
1056 debug!(
1057 received_logs_len = %received_logs.len(),
1058 received_logs_total = %received_logs.iter().map(|x| x.1.len()).sum::<usize>(),
1059 "collected received logs"
1060 );
1061
1062 let (received_logs, mut validator_trackers) = {
1063 (
1064 ReceivedLogs::from_received_result(received_logs.clone()),
1065 ValidatorTrackers::new(received_logs, &trackers),
1066 )
1067 };
1068
1069 debug!(
1070 num_chains = %received_logs.num_chains(),
1071 num_certs = %received_logs.num_certs(),
1072 "find_received_certificates: total number of chains and certificates to sync",
1073 );
1074
1075 let max_blocks_per_chain =
1076 self.options.sender_certificate_download_batch_size / self.options.max_joined_tasks * 2;
1077 for received_log in received_logs.into_batches(
1078 self.options.sender_certificate_download_batch_size,
1079 max_blocks_per_chain,
1080 ) {
1081 validator_trackers = self
1082 .receive_sender_certificates(received_log, validator_trackers, &nodes)
1083 .await?;
1084
1085 self.update_received_certificate_trackers(&validator_trackers)
1086 .await;
1087 }
1088
1089 trace!("find_received_certificates finished");
1090
1091 Ok(())
1092 }
1093
1094 async fn update_received_certificate_trackers(&self, trackers: &ValidatorTrackers) {
1095 let updated_trackers = trackers.to_map();
1096 trace!(?updated_trackers, "updated tracker values");
1097
1098 if let Err(error) = self
1100 .client
1101 .local_node
1102 .update_received_certificate_trackers(self.chain_id, updated_trackers)
1103 .await
1104 {
1105 error!(
1106 chain_id = %self.chain_id,
1107 %error,
1108 "Failed to update the certificate trackers",
1109 );
1110 }
1111 }
1112
1113 async fn receive_sender_certificates(
1116 &self,
1117 mut received_logs: ReceivedLogs,
1118 mut validator_trackers: ValidatorTrackers,
1119 nodes: &[RemoteNode<Env::ValidatorNode>],
1120 ) -> Result<ValidatorTrackers, Error> {
1121 debug!(
1122 num_chains = %received_logs.num_chains(),
1123 num_certs = %received_logs.num_certs(),
1124 "receive_sender_certificates: number of chains and certificates to sync",
1125 );
1126
1127 let local_next_heights = self
1129 .client
1130 .local_node
1131 .next_outbox_heights(received_logs.chains(), self.chain_id)
1132 .await?;
1133
1134 validator_trackers.filter_out_already_known(&mut received_logs, &local_next_heights);
1135
1136 debug!(
1137 remaining_total_certificates = %received_logs.num_certs(),
1138 "receive_sender_certificates: computed remote_heights"
1139 );
1140
1141 let mut other_sender_chains = Vec::new();
1142 let (sender, mut receiver) = mpsc::unbounded_channel::<ChainAndHeight>();
1143
1144 let cert_futures = received_logs.heights_per_chain().into_iter().filter_map({
1145 let received_logs = &received_logs;
1146 let other_sender_chains = &mut other_sender_chains;
1147
1148 move |(sender_chain_id, remote_heights)| {
1149 if remote_heights.is_empty() {
1150 other_sender_chains.push(sender_chain_id);
1154 return None;
1155 };
1156 let remote_heights = remote_heights.into_iter().collect::<Vec<_>>();
1157 let sender = sender.clone();
1158 let client = self.client.clone();
1159 let nodes = nodes.to_vec();
1160 Some(async move {
1161 client
1162 .download_and_process_sender_chain(
1163 sender_chain_id,
1164 &nodes,
1165 received_logs,
1166 remote_heights,
1167 sender,
1168 )
1169 .await
1170 })
1171 }
1172 });
1173
1174 future::join(
1175 stream::iter(cert_futures)
1176 .buffer_unordered(self.options.max_joined_tasks)
1177 .collect::<()>(),
1178 async {
1179 while let Some(chain_and_height) = receiver.recv().await {
1180 validator_trackers.downloaded_cert(chain_and_height);
1181 }
1182 },
1183 )
1184 .await;
1185
1186 debug!(
1187 num_other_chains = %other_sender_chains.len(),
1188 "receive_sender_certificates: processing certificates finished"
1189 );
1190
1191 self.retry_pending_cross_chain_requests_from_sender_chains(nodes, other_sender_chains)
1195 .await;
1196
1197 debug!("receive_sender_certificates: finished processing other_sender_chains");
1198
1199 Ok(validator_trackers)
1200 }
1201
1202 async fn retry_pending_cross_chain_requests_from_sender_chains(
1206 &self,
1207 nodes: &[RemoteNode<Env::ValidatorNode>],
1208 other_sender_chains: Vec<ChainId>,
1209 ) {
1210 let stream = other_sender_chains
1211 .into_iter()
1212 .map(|chain_id| async move {
1213 if let Err(error) = match self
1214 .client
1215 .retry_pending_cross_chain_requests(chain_id)
1216 .await
1217 {
1218 Ok(()) => Ok(()),
1219 Err(LocalNodeError::BlobsNotFound(blob_ids)) => {
1220 if let Err(error) = self
1221 .client
1222 .update_local_node_with_blobs_from(blob_ids.clone(), nodes)
1223 .await
1224 {
1225 error!(
1226 ?blob_ids,
1227 %error,
1228 "Error while attempting to download blobs during retrying outgoing \
1229 messages"
1230 );
1231 }
1232 self.client
1233 .retry_pending_cross_chain_requests(chain_id)
1234 .await
1235 }
1236 err => err,
1237 } {
1238 error!(
1239 %chain_id,
1240 %error,
1241 "Failed to retry outgoing messages from chain"
1242 );
1243 }
1244 })
1245 .collect::<FuturesUnordered<_>>();
1246 stream.for_each(future::ready).await;
1247 }
1248
1249 #[instrument(level = "trace")]
1251 pub async fn transfer(
1252 &self,
1253 owner: AccountOwner,
1254 amount: Amount,
1255 recipient: Account,
1256 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1257 self.execute_operation(SystemOperation::Transfer {
1259 owner,
1260 recipient,
1261 amount,
1262 })
1263 .await
1264 }
1265
1266 #[instrument(level = "trace")]
1269 pub async fn read_data_blob(
1270 &self,
1271 hash: CryptoHash,
1272 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1273 let blob_id = BlobId {
1274 hash,
1275 blob_type: BlobType::Data,
1276 };
1277 self.execute_operation(SystemOperation::VerifyBlob { blob_id })
1278 .await
1279 }
1280
1281 #[instrument(level = "trace")]
1283 pub async fn claim(
1284 &self,
1285 owner: AccountOwner,
1286 target_id: ChainId,
1287 recipient: Account,
1288 amount: Amount,
1289 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1290 self.execute_operation(SystemOperation::Claim {
1291 owner,
1292 target_id,
1293 recipient,
1294 amount,
1295 })
1296 .await
1297 }
1298
1299 #[instrument(level = "trace")]
1302 pub async fn request_leader_timeout(&self) -> Result<TimeoutCertificate, Error> {
1303 let chain_id = self.chain_id;
1304 let info = self.chain_info().await?;
1305 let committee = self.local_committee().await?;
1306 let height = info.next_block_height;
1307 let round = info.manager.current_round;
1308 let action = CommunicateAction::RequestTimeout {
1309 height,
1310 round,
1311 chain_id,
1312 };
1313 let value = Timeout::new(chain_id, height, info.epoch);
1314 let certificate = Box::new(
1315 self.client
1316 .communicate_chain_action(&committee, action, value)
1317 .await?,
1318 );
1319 self.client
1320 .handle_certificate::<Timeout>(*certificate.clone())
1321 .await?;
1322 self.client
1324 .communicate_chain_updates(
1325 &committee,
1326 chain_id,
1327 height,
1328 CrossChainMessageDelivery::NonBlocking,
1329 None,
1330 )
1331 .await?;
1332 Ok(*certificate)
1333 }
1334
1335 #[instrument(level = "trace", skip_all)]
1337 pub async fn synchronize_chain_state(
1338 &self,
1339 chain_id: ChainId,
1340 ) -> Result<Box<ChainInfo>, Error> {
1341 self.client.synchronize_chain_state(chain_id).await
1342 }
1343
1344 #[instrument(level = "trace", skip_all)]
1347 pub async fn synchronize_chain_state_from_committee(
1348 &self,
1349 committee: Arc<Committee>,
1350 ) -> Result<Box<ChainInfo>, Error> {
1351 self.client
1352 .synchronize_chain_from_committee(self.chain_id, committee)
1353 .await
1354 }
1355
1356 #[instrument(level = "trace", skip(operations, blobs))]
1358 pub async fn execute_operations(
1359 &self,
1360 operations: Vec<Operation>,
1361 blobs: Vec<Blob>,
1362 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1363 let timing_start = linera_base::time::Instant::now();
1364
1365 let result = loop {
1366 let execute_block_start = linera_base::time::Instant::now();
1367 match Box::pin(self.execute_block(operations.clone(), blobs.clone())).await {
1369 Ok(ClientOutcome::Committed(certificate)) => {
1370 self.send_timing(execute_block_start, TimingType::ExecuteBlock);
1371 break Ok(ClientOutcome::Committed(certificate));
1372 }
1373 Ok(ClientOutcome::WaitForTimeout(timeout)) => {
1374 break Ok(ClientOutcome::WaitForTimeout(timeout));
1375 }
1376 Ok(ClientOutcome::Conflict(certificate)) => {
1377 info!(
1378 height = %certificate.block().header.height,
1379 "Another block was committed."
1380 );
1381 break Ok(ClientOutcome::Conflict(certificate));
1382 }
1383 Err(Error::CommunicationError(CommunicationError::Trusted(
1384 NodeError::UnexpectedBlockHeight {
1385 expected_block_height,
1386 found_block_height,
1387 },
1388 ))) if expected_block_height > found_block_height => {
1389 tracing::info!(
1390 chain_id = %self.chain_id,
1391 "Local state is outdated; synchronizing chain"
1392 );
1393 self.synchronize_chain_state(self.chain_id).await?;
1394 }
1395 Err(err) => return Err(err),
1396 };
1397 };
1398
1399 self.send_timing(timing_start, TimingType::ExecuteOperations);
1400
1401 result
1402 }
1403
1404 pub async fn execute_operation(
1406 &self,
1407 operation: impl Into<Operation>,
1408 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1409 self.execute_operations(vec![operation.into()], vec![])
1410 .await
1411 }
1412
1413 #[instrument(level = "trace", skip(operations, blobs))]
1417 async fn execute_block(
1418 &self,
1419 operations: Vec<Operation>,
1420 blobs: Vec<Blob>,
1421 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1422 #[cfg(with_metrics)]
1423 let _latency = super::metrics::EXECUTE_BLOCK_LATENCY.measure_latency();
1424
1425 let result = self.try_execute_block(operations, blobs).await;
1426 if let Err(error) = &result {
1427 let error_type = error.error_type();
1428 #[cfg(with_metrics)]
1429 super::metrics::BLOCK_STAGING_FAILURES_TOTAL
1430 .with_label_values(&[error_type.as_str()])
1431 .inc();
1432 info!(chain_id = %self.chain_id, %error_type, "Block staging failed");
1433 }
1434 result
1435 }
1436
1437 async fn try_execute_block(
1438 &self,
1439 operations: Vec<Operation>,
1440 blobs: Vec<Blob>,
1441 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1442 let mutex = self.proposal_mutex();
1443 let lock_start = linera_base::time::Instant::now();
1444 let mut proposal_guard = mutex.lock_owned().await;
1445 tracing::debug!(
1446 chain_id = %self.chain_id,
1447 lock_wait_ms = lock_start.elapsed().as_millis(),
1448 "acquired proposal_mutex in execute_block"
1449 );
1450 match self
1456 .process_pending_block_without_prepare(&mut proposal_guard)
1457 .await?
1458 {
1459 ClientOutcome::Committed(Some(certificate)) => {
1460 return Ok(self.classify_committed(certificate, &operations));
1461 }
1462 ClientOutcome::WaitForTimeout(timeout) => {
1463 return Ok(ClientOutcome::WaitForTimeout(timeout))
1464 }
1465 ClientOutcome::Conflict(certificate) => {
1466 return Ok(ClientOutcome::Conflict(certificate))
1467 }
1468 ClientOutcome::Committed(None) => {}
1469 }
1470
1471 loop {
1472 let transactions = self
1477 .prepend_epochs_messages_and_events(operations.clone())
1478 .await?;
1479
1480 if transactions.is_empty() {
1481 return Err(Error::LocalNodeError(LocalNodeError::WorkerError(
1482 WorkerError::ChainError(Box::new(ChainError::EmptyBlock)),
1483 )));
1484 }
1485
1486 self.new_pending_block(transactions, blobs.clone(), &mut proposal_guard)
1487 .await?;
1488
1489 match self
1490 .process_pending_block_without_prepare(&mut proposal_guard)
1491 .await?
1492 {
1493 ClientOutcome::Committed(Some(certificate)) => {
1494 return Ok(self.classify_committed(certificate, &operations));
1495 }
1496 ClientOutcome::Committed(None) => {
1510 tracing::debug!(
1511 chain_id = %self.chain_id,
1512 "pending proposal cleared without committing ours; re-staging and retrying"
1513 );
1514 continue;
1515 }
1516 ClientOutcome::WaitForTimeout(timeout) => {
1517 return Ok(ClientOutcome::WaitForTimeout(timeout));
1518 }
1519 ClientOutcome::Conflict(certificate) => {
1520 return Ok(ClientOutcome::Conflict(certificate));
1521 }
1522 }
1523 }
1524 }
1525
1526 fn classify_committed(
1530 &self,
1531 certificate: ConfirmedBlockCertificate,
1532 operations: &[Operation],
1533 ) -> ClientOutcome<ConfirmedBlockCertificate> {
1534 let block = certificate.block();
1535 if self.preferred_owner.is_none()
1536 || block.header.authenticated_owner != self.preferred_owner
1537 {
1538 return ClientOutcome::Conflict(Box::new(certificate));
1539 }
1540 let mut operations_iter = operations.iter().peekable();
1541 for tx in &block.body.transactions {
1542 let is_expected = match tx {
1543 Transaction::ReceiveMessages(_) => true,
1544 Transaction::ExecuteOperation(op) if Some(&op) == operations_iter.peek() => {
1545 operations_iter.next();
1546 true
1547 }
1548 Transaction::ExecuteOperation(Operation::System(op)) => matches!(
1549 **op,
1550 SystemOperation::ProcessNewEpoch(_) | SystemOperation::UpdateStream { .. }
1551 ),
1552 Transaction::ExecuteOperation(Operation::User { .. }) => false,
1553 };
1554 if !is_expected {
1555 return ClientOutcome::Conflict(Box::new(certificate));
1556 }
1557 }
1558 if operations_iter.next().is_some() {
1559 ClientOutcome::Conflict(Box::new(certificate))
1560 } else {
1561 ClientOutcome::Committed(certificate)
1562 }
1563 }
1564
1565 #[instrument(level = "trace", skip(operations))]
1571 async fn prepend_epochs_messages_and_events(
1572 &self,
1573 operations: Vec<Operation>,
1574 ) -> Result<Vec<Transaction>, Error> {
1575 let incoming_bundles = self.pending_message_bundles().await?;
1576 let stream_updates = self.collect_stream_updates().await?;
1577 Ok(self
1578 .next_epoch_change()
1579 .await?
1580 .into_iter()
1581 .map(Transaction::ExecuteOperation)
1582 .chain(
1583 incoming_bundles
1584 .into_iter()
1585 .map(Transaction::ReceiveMessages),
1586 )
1587 .chain(
1588 stream_updates
1589 .into_iter()
1590 .map(Transaction::ExecuteOperation),
1591 )
1592 .chain(operations.into_iter().map(Transaction::ExecuteOperation))
1593 .collect::<Vec<_>>())
1594 }
1595
1596 #[instrument(level = "trace", skip(transactions, blobs, proposal_guard))]
1601 async fn new_pending_block(
1602 &self,
1603 transactions: Vec<Transaction>,
1604 blobs: Vec<Blob>,
1605 proposal_guard: &mut Option<PendingProposal>,
1606 ) -> Result<Block, Error> {
1607 let identity = self.identity().await?;
1608
1609 ensure!(
1610 proposal_guard.is_none(),
1611 Error::BlockProposalError(
1612 "Client state already has a pending block; \
1613 use the `linera retry-pending-block` command to commit that first"
1614 )
1615 );
1616 let info = self.chain_info().await?;
1617 let timestamp = self.next_timestamp(&transactions, info.timestamp);
1618 let proposed_block = ProposedBlock {
1619 epoch: info.epoch,
1620 chain_id: self.chain_id,
1621 transactions,
1622 previous_block_hash: info.block_hash,
1623 height: info.next_block_height,
1624 authenticated_owner: Some(identity),
1625 timestamp,
1626 };
1627
1628 let round = self.round_for_oracle(&info, &identity).await?;
1629 let (block, _, never_reject_origins) = self
1632 .client
1633 .stage_block_execution(
1634 proposed_block,
1635 round,
1636 blobs.clone(),
1637 self.options.bundle_execution_policy(),
1638 )
1639 .await?;
1640 if !never_reject_origins.is_empty() {
1643 let skipped = self.skipped_origins.pin();
1644 for origin in never_reject_origins {
1645 skipped.insert(origin);
1646 }
1647 }
1648 let (proposed_block, auto_retry_outcome) = block.clone().into_proposal();
1649 *proposal_guard = Some(PendingProposal {
1650 block: proposed_block,
1651 blobs,
1652 auto_retry_outcome: Some(auto_retry_outcome),
1653 round: None,
1654 });
1655 Ok(block)
1656 }
1657
1658 #[instrument(level = "trace", skip(transactions))]
1663 fn next_timestamp(&self, transactions: &[Transaction], block_time: Timestamp) -> Timestamp {
1664 let local_time = self.storage_client().clock().current_time();
1665 transactions
1666 .iter()
1667 .filter_map(Transaction::incoming_bundle)
1668 .map(|msg| msg.bundle.timestamp)
1669 .max()
1670 .map_or(local_time, |timestamp| timestamp.max(local_time))
1671 .max(block_time)
1672 }
1673
1674 #[instrument(level = "trace", skip(query))]
1676 pub async fn query_application(
1677 &self,
1678 query: Query,
1679 block_hash: Option<CryptoHash>,
1680 ) -> Result<(QueryOutcome, BlockHeight), Error> {
1681 let mut downloaded_blobs = HashSet::<BlobId>::new();
1682 let mut events = super::EventSetDownloader::new(&self.client);
1683 loop {
1684 let result = self
1685 .client
1686 .local_node
1687 .query_application(self.chain_id, query.clone(), block_hash)
1688 .await;
1689 if let Err(LocalNodeError::BlobsNotFound(blob_ids)) = &result {
1690 let new_blobs = super::filter_new(blob_ids, &downloaded_blobs);
1691 if !new_blobs.is_empty() {
1692 let validators = self.client.validator_nodes().await?;
1693 self.client
1694 .update_local_node_with_blobs_from(new_blobs.clone(), &validators)
1695 .await?;
1696 downloaded_blobs.extend(new_blobs);
1697 continue;
1698 }
1699 }
1700 if let Err(LocalNodeError::EventsNotFound(event_ids)) = &result {
1701 if events.download_new(event_ids).await? {
1702 continue;
1703 }
1704 }
1705 return Ok(result?);
1706 }
1707 }
1708
1709 #[cfg(with_testing)]
1711 #[instrument(level = "trace", skip(query))]
1712 pub async fn query_system_application(
1713 &self,
1714 query: linera_execution::SystemQuery,
1715 ) -> Result<QueryOutcome<linera_execution::SystemResponse>, Error> {
1716 let (
1717 QueryOutcome {
1718 response,
1719 operations,
1720 },
1721 _,
1722 ) = self.query_application(Query::System(query), None).await?;
1723 match response {
1724 linera_execution::QueryResponse::System(response) => Ok(QueryOutcome {
1725 response,
1726 operations,
1727 }),
1728 _ => Err(Error::InternalError("Unexpected response for system query")),
1729 }
1730 }
1731
1732 #[instrument(level = "trace", skip(application_id, query))]
1734 #[cfg(with_testing)]
1735 pub async fn query_user_application<A: Abi>(
1736 &self,
1737 application_id: ApplicationId<A>,
1738 query: &A::Query,
1739 ) -> Result<QueryOutcome<A::QueryResponse>, Error> {
1740 let query = Query::user(application_id, query)?;
1741 let (
1742 QueryOutcome {
1743 response,
1744 operations,
1745 },
1746 _,
1747 ) = self.query_application(query, None).await?;
1748 match response {
1749 linera_execution::QueryResponse::User(response_bytes) => {
1750 let response = serde_json::from_slice(&response_bytes)?;
1751 Ok(QueryOutcome {
1752 response,
1753 operations,
1754 })
1755 }
1756 _ => Err(Error::InternalError("Unexpected response for user query")),
1757 }
1758 }
1759
1760 #[instrument(level = "trace")]
1767 pub async fn query_balance(&self) -> Result<Amount, Error> {
1768 let (balance, _) = self.query_balances_with_owner(AccountOwner::CHAIN).await?;
1769 Ok(balance)
1770 }
1771
1772 #[instrument(level = "trace", skip(owner))]
1779 pub async fn query_owner_balance(&self, owner: AccountOwner) -> Result<Amount, Error> {
1780 if owner.is_chain() {
1781 self.query_balance().await
1782 } else {
1783 Ok(self
1784 .query_balances_with_owner(owner)
1785 .await?
1786 .1
1787 .unwrap_or(Amount::ZERO))
1788 }
1789 }
1790
1791 #[instrument(level = "trace", skip(owner))]
1798 pub(crate) async fn query_balances_with_owner(
1799 &self,
1800 owner: AccountOwner,
1801 ) -> Result<(Amount, Option<Amount>), Error> {
1802 let incoming_bundles = self.pending_message_bundles().await?;
1803 if incoming_bundles.is_empty() {
1806 let chain_balance = self.local_balance().await?;
1807 let owner_balance = self.local_owner_balance(owner).await?;
1808 return Ok((chain_balance, Some(owner_balance)));
1809 }
1810 let info = self.chain_info().await?;
1811 let transactions = incoming_bundles
1812 .into_iter()
1813 .map(Transaction::ReceiveMessages)
1814 .collect::<Vec<_>>();
1815 let timestamp = self.next_timestamp(&transactions, info.timestamp);
1816 let block = ProposedBlock {
1817 epoch: info.epoch,
1818 chain_id: self.chain_id,
1819 transactions,
1820 previous_block_hash: info.block_hash,
1821 height: info.next_block_height,
1822 authenticated_owner: if owner == AccountOwner::CHAIN {
1823 None
1824 } else {
1825 Some(owner)
1826 },
1827 timestamp,
1828 };
1829 match self
1830 .client
1831 .stage_block_execution(
1832 block,
1833 None,
1834 Vec::new(),
1835 self.options.bundle_execution_policy(),
1836 )
1837 .await
1838 {
1839 Ok((_, response, _)) => Ok((
1840 response.info.chain_balance,
1841 response.info.requested_owner_balance,
1842 )),
1843 Err(Error::LocalNodeError(LocalNodeError::WorkerError(WorkerError::ChainError(
1844 error,
1845 )))) if matches!(
1846 &*error,
1847 ChainError::ExecutionError(
1848 execution_error,
1849 ChainExecutionContext::Block
1850 ) if matches!(
1851 **execution_error,
1852 ExecutionError::FeesExceedFunding { .. }
1853 )
1854 ) =>
1855 {
1856 Ok((Amount::ZERO, Some(Amount::ZERO)))
1858 }
1859 Err(error) => Err(error),
1860 }
1861 }
1862
1863 #[instrument(level = "trace")]
1867 pub async fn local_balance(&self) -> Result<Amount, Error> {
1868 let (balance, _) = self.local_balances_with_owner(AccountOwner::CHAIN).await?;
1869 Ok(balance)
1870 }
1871
1872 #[instrument(level = "trace", skip(owner))]
1876 pub async fn local_owner_balance(&self, owner: AccountOwner) -> Result<Amount, Error> {
1877 if owner.is_chain() {
1878 self.local_balance().await
1879 } else {
1880 Ok(self
1881 .local_balances_with_owner(owner)
1882 .await?
1883 .1
1884 .unwrap_or(Amount::ZERO))
1885 }
1886 }
1887
1888 #[instrument(level = "trace", skip(owner))]
1892 pub(crate) async fn local_balances_with_owner(
1893 &self,
1894 owner: AccountOwner,
1895 ) -> Result<(Amount, Option<Amount>), Error> {
1896 ensure!(
1897 self.chain_info().await?.next_block_height >= self.initial_next_block_height,
1898 Error::WalletSynchronizationError
1899 );
1900 let mut query = ChainInfoQuery::new(self.chain_id);
1901 query.request_owner_balance = owner;
1902 let response = self
1903 .client
1904 .local_node
1905 .handle_chain_info_query(query)
1906 .await?;
1907 Ok((
1908 response.info.chain_balance,
1909 response.info.requested_owner_balance,
1910 ))
1911 }
1912
1913 #[instrument(level = "trace")]
1915 pub async fn transfer_to_account(
1916 &self,
1917 from: AccountOwner,
1918 amount: Amount,
1919 account: Account,
1920 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1921 self.transfer(from, amount, account).await
1922 }
1923
1924 #[cfg(with_testing)]
1926 #[instrument(level = "trace")]
1927 pub async fn burn(
1928 &self,
1929 owner: AccountOwner,
1930 amount: Amount,
1931 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1932 let recipient = Account::burn_address(self.chain_id);
1933 self.transfer(owner, amount, recipient).await
1934 }
1935
1936 #[instrument(level = "trace")]
1938 pub async fn fetch_chain_info(&self) -> Result<Box<ChainInfo>, Error> {
1939 let validators = self.client.validator_nodes().await?;
1940 self.client
1941 .fetch_chain_info(self.chain_id, &validators)
1942 .await
1943 }
1944
1945 #[instrument(level = "trace")]
1960 pub async fn synchronize_up_to(
1961 &self,
1962 next_height: Option<BlockHeight>,
1963 until_block_time: Option<Timestamp>,
1964 ) -> Result<Box<ChainInfo>, Error> {
1965 let (_, committee) = self.client.admin_committee().await?;
1966 let validators = self.client.make_nodes(&committee)?;
1967 Box::pin(self.client.fetch_chain_info(self.chain_id, &validators)).await?;
1968 communicate_with_quorum(
1969 &validators,
1970 &committee,
1971 |_: &()| (),
1972 |remote_node| async move {
1973 self.client
1974 .download_certificates_from(
1975 &remote_node,
1976 self.chain_id,
1977 next_height.unwrap_or(BlockHeight::MAX),
1978 until_block_time,
1979 )
1980 .await?;
1981 Ok(())
1982 },
1983 self.client.options.quorum_grace_period,
1984 )
1985 .await?;
1986 self.client
1987 .local_node
1988 .chain_info(self.chain_id)
1989 .await
1990 .map_err(Into::into)
1991 }
1992
1993 pub async fn synchronize_from_validators(&self) -> Result<Box<ChainInfo>, Error> {
1995 if self.is_follow_only() {
1996 return self.client.synchronize_chain_state(self.chain_id).await;
1997 }
1998 let info = self.prepare_chain().await?;
1999 self.synchronize_publisher_chains().await?;
2000 self.find_received_certificates().await?;
2001 Ok(info)
2002 }
2003
2004 #[instrument(level = "trace")]
2006 pub async fn process_pending_block(
2007 &self,
2008 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2009 self.prepare_chain().await?;
2010 let mutex = self.proposal_mutex();
2011 let mut proposal_guard = mutex.lock_owned().await;
2012 self.process_pending_block_without_prepare(&mut proposal_guard)
2013 .await
2014 }
2015
2016 #[instrument(level = "debug", skip(self, proposal_guard), fields(chain_id = %self.chain_id))]
2031 async fn process_pending_block_without_prepare(
2032 &self,
2033 proposal_guard: &mut Option<PendingProposal>,
2034 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2035 let mut did_fallback_sync = false;
2037 loop {
2038 let mut snapshot = None;
2043 let err = match self
2044 .process_pending_block_inner(proposal_guard, &mut snapshot)
2045 .await
2046 {
2047 Ok(outcome) => return Ok(outcome),
2048 Err(err) => err,
2049 };
2050 let Some(snapshot) = snapshot else {
2051 return Err(err);
2052 };
2053 let Ok(current) = self.consensus_state_snapshot().await else {
2054 return Err(err);
2055 };
2056 if current == snapshot {
2057 if did_fallback_sync {
2069 return Err(err);
2070 }
2071 did_fallback_sync = true;
2072 if let Err(error) = self.synchronize_chain_state(self.chain_id).await {
2073 tracing::error!(%error, "fallback sync failed after rejected proposal");
2074 return Err(err);
2075 }
2076 let Ok(after_sync) = self.consensus_state_snapshot().await else {
2077 return Err(err);
2078 };
2079 if after_sync == snapshot {
2080 return Err(err);
2081 }
2082 tracing::debug!(
2083 chain_id = %self.chain_id,
2084 ?snapshot,
2085 ?after_sync,
2086 %err,
2087 "fallback sync absorbed new consensus state after rejected proposal; retrying"
2088 );
2089 continue;
2090 }
2091 tracing::debug!(
2092 chain_id = %self.chain_id,
2093 ?snapshot,
2094 ?current,
2095 %err,
2096 "local consensus state advanced during process_pending_block; retrying"
2097 );
2098 }
2099 }
2100
2101 async fn process_pending_block_inner(
2102 &self,
2103 proposal_guard: &mut Option<PendingProposal>,
2104 snapshot: &mut Option<ConsensusStateSnapshot>,
2105 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2106 let process_start = linera_base::time::Instant::now();
2107 tracing::debug!("process_pending_block_without_prepare started");
2108 let info = self.request_leader_timeout_if_needed().await?;
2109 *snapshot = Some(self.consensus_state_snapshot().await?);
2114
2115 if let Some(pending) = &*proposal_guard {
2117 if pending.block.height < info.next_block_height {
2118 tracing::debug!(
2119 "Clearing pending proposal: a block was committed at height {}",
2120 pending.block.height
2121 );
2122 *proposal_guard = None;
2123 }
2124 }
2125
2126 if info.manager.has_locking_block_in_current_round()
2128 && !info.manager.current_round.is_fast()
2129 {
2130 return self.finalize_locking_block(info).await;
2131 }
2132 let identity = self.identity().await?;
2133
2134 let local_node = &self.client.local_node;
2135 let (block, blobs, owner) = if let Some(locking) = &info.manager.requested_locking {
2137 let (block, blobs) = match &**locking {
2138 LockingBlock::Regular(certificate) => {
2139 let blob_ids = certificate.block().required_blob_ids();
2140 let blobs = local_node
2141 .get_locking_blobs(&blob_ids, self.chain_id)
2142 .await?
2143 .ok_or_else(|| Error::InternalError("Missing local locking blobs"))?;
2144 debug!("Retrying locking block from round {}", certificate.round);
2145 (certificate.block().clone(), blobs)
2146 }
2147 LockingBlock::Fast(proposal) => {
2148 let proposed_block = proposal.content.block.clone();
2149 let blob_ids = proposed_block.published_blob_ids();
2150 let blobs = local_node
2151 .get_locking_blobs(&blob_ids, self.chain_id)
2152 .await?
2153 .ok_or_else(|| Error::InternalError("Missing local locking blobs"))?;
2154 let (block, _, _) = self
2155 .client
2156 .stage_block_execution(
2157 proposed_block,
2158 None,
2159 blobs.clone(),
2160 BundleExecutionPolicy::committed(),
2161 )
2162 .await?;
2163 debug!("Retrying locking block from fast round.");
2164 (block, blobs)
2165 }
2166 };
2167 (block, blobs, identity)
2168 } else if let Some(pending) = proposal_guard.as_ref() {
2169 let owner = match pending.block.authenticated_owner {
2176 Some(staged_owner) if staged_owner != identity => {
2177 if !self.has_key_for(&staged_owner).await? {
2178 if pending.round.is_some_and(|round| round.is_fast()) {
2185 return Err(Error::BlockProposalError(
2186 "pending fast block was signed by an owner whose key is no \
2187 longer available; recover the key or wait for the round to \
2188 time out before retrying",
2189 ));
2190 }
2191 warn!(
2192 ?staged_owner, %identity,
2193 "Discarding pending block: no signer key for its authenticated owner",
2194 );
2195 *proposal_guard = None;
2196 return Ok(ClientOutcome::Committed(None));
2197 }
2198 staged_owner
2199 }
2200 _ => identity,
2201 };
2202 let proposed_block = pending.block.clone();
2203 let blobs = pending.blobs.clone();
2204 let staging_outcome = pending.auto_retry_outcome.as_ref();
2205 let round = self.round_for_oracle(&info, &owner).await?;
2206 let (block, _, _) = self
2207 .client
2208 .stage_block_execution(
2209 proposed_block,
2210 round,
2211 blobs.clone(),
2212 BundleExecutionPolicy::committed(),
2213 )
2214 .await?;
2215 if let Some(staging_outcome) = staging_outcome {
2219 ensure!(
2220 block.outcome_matches(staging_outcome),
2221 Error::ExecutionOutcomeMismatch
2222 );
2223 }
2224 debug!("Proposing the local pending block.");
2225 (block, blobs, owner)
2226 } else {
2227 return Ok(ClientOutcome::Committed(None)); };
2229
2230 let has_oracle_responses = block.has_oracle_responses();
2231 let (proposed_block, outcome) = block.into_proposal();
2232 let round = match self
2233 .round_for_new_proposal(&info, &owner, has_oracle_responses)
2234 .await?
2235 {
2236 Either::Left(round) => round,
2237 Either::Right(timeout) => return Ok(ClientOutcome::WaitForTimeout(timeout)),
2238 };
2239 debug!("Proposing block for round {}", round);
2240 if let Some(pending) = proposal_guard.as_mut() {
2241 pending.round.get_or_insert(round);
2242 }
2243
2244 let already_handled_locally = info
2245 .manager
2246 .already_handled_proposal(round, &proposed_block);
2247 let proposal = if let Some(locking) = info.manager.requested_locking {
2249 Box::new(match *locking {
2250 LockingBlock::Regular(cert) => {
2251 BlockProposal::new_retry_regular(owner, round, cert, self.signer())
2252 .await
2253 .map_err(Error::signer_failure)?
2254 }
2255 LockingBlock::Fast(proposal) => {
2256 BlockProposal::new_retry_fast(owner, round, proposal, self.signer())
2257 .await
2258 .map_err(Error::signer_failure)?
2259 }
2260 })
2261 } else {
2262 Box::new(
2263 BlockProposal::new_initial(owner, round, proposed_block.clone(), self.signer())
2264 .await
2265 .map_err(Error::signer_failure)?,
2266 )
2267 };
2268 if !already_handled_locally {
2269 if let Err(err) = local_node.handle_block_proposal(*proposal.clone()).await {
2271 match err {
2272 LocalNodeError::BlobsNotFound(_) => {
2273 local_node
2274 .handle_pending_blobs(self.chain_id, blobs)
2275 .await?;
2276 local_node.handle_block_proposal(*proposal.clone()).await?;
2277 }
2278 err => return Err(err.into()),
2279 }
2280 }
2281 }
2282 *snapshot = Some(self.consensus_state_snapshot().await?);
2287 let committee = self.local_committee().await?;
2288 let block = Block::new(proposed_block, outcome);
2289 let submit_block_proposal_start = linera_base::time::Instant::now();
2291 let certificate = if round.is_fast() {
2292 let hashed_value = ConfirmedBlock::new(block);
2293 self.client
2294 .submit_block_proposal(committee.clone(), proposal, hashed_value)
2295 .await?
2296 } else {
2297 let hashed_value = ValidatedBlock::new(block);
2298 let certificate = self
2299 .client
2300 .submit_block_proposal(committee.clone(), proposal, hashed_value.clone())
2301 .await?;
2302 self.client.finalize_block(&committee, certificate).await?
2303 };
2304 self.send_timing(submit_block_proposal_start, TimingType::SubmitBlockProposal);
2305 tracing::debug!(
2306 total_process_ms = process_start.elapsed().as_millis(),
2307 "process_pending_block_without_prepare completing"
2308 );
2309 debug!(round = %certificate.round, "Sending confirmed block to validators");
2310 let certificate = self.client.storage_client().cache_certificate(certificate);
2311 self.update_validators(Some(&committee), Some(certificate.clone()))
2312 .await?;
2313 *proposal_guard = None;
2315 Ok(ClientOutcome::Committed(Some(CacheArc::unwrap_or_clone(
2316 certificate,
2317 ))))
2318 }
2319
2320 #[expect(
2321 clippy::cast_possible_truncation,
2322 reason = "elapsed millis fits in u64 for any realistic measurement window"
2323 )]
2324 fn send_timing(&self, start: Instant, timing_type: TimingType) {
2325 let Some(sender) = &self.timing_sender else {
2326 return;
2327 };
2328 if let Err(err) = sender.send((start.elapsed().as_millis() as u64, timing_type)) {
2329 tracing::warn!(%err, "Failed to send timing info");
2330 }
2331 }
2332
2333 async fn request_leader_timeout_if_needed(&self) -> Result<Box<ChainInfo>, Error> {
2336 let mut info = self.chain_info_with_manager_values().await?;
2337 if let Some(round_timeout) = info.manager.round_timeout {
2340 if round_timeout <= self.storage_client().clock().current_time() {
2341 if let Err(e) = self.request_leader_timeout().await {
2342 debug!("Failed to obtain a timeout certificate: {}", e);
2343 } else {
2344 info = self.chain_info_with_manager_values().await?;
2345 }
2346 }
2347 }
2348 Ok(info)
2349 }
2350
2351 async fn finalize_locking_block(
2355 &self,
2356 info: Box<ChainInfo>,
2357 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2358 let locking = info
2359 .manager
2360 .requested_locking
2361 .expect("Should have a locking block");
2362 let LockingBlock::Regular(certificate) = *locking else {
2363 panic!("Should have a locking validated block");
2364 };
2365 debug!(
2366 round = %certificate.round,
2367 "Finalizing locking block"
2368 );
2369 let committee = self.local_committee().await?;
2370 let certificate = self
2371 .client
2372 .finalize_block(&committee, certificate.clone())
2373 .await?;
2374 let certificate = self.client.storage_client().cache_certificate(certificate);
2375 self.update_validators(Some(&committee), Some(certificate.clone()))
2376 .await?;
2377 Ok(ClientOutcome::Committed(Some(CacheArc::unwrap_or_clone(
2378 certificate,
2379 ))))
2380 }
2381
2382 async fn round_for_oracle(
2384 &self,
2385 info: &ChainInfo,
2386 identity: &AccountOwner,
2387 ) -> Result<Option<u32>, Error> {
2388 match self.round_for_new_proposal(info, identity, true).await {
2390 Ok(Either::Left(round)) => Ok(round.multi_leader()),
2392 Err(Error::BlockProposalError(_)) | Ok(Either::Right(_)) => Ok(None),
2396 Err(err) => Err(err),
2397 }
2398 }
2399
2400 async fn round_for_new_proposal(
2402 &self,
2403 info: &ChainInfo,
2404 identity: &AccountOwner,
2405 has_oracle_responses: bool,
2406 ) -> Result<Either<Round, RoundTimeout>, Error> {
2407 let manager = &info.manager;
2408 let seed = manager.seed;
2409 let skip_fast = manager.current_round.is_fast()
2414 && (has_oracle_responses || !self.options.allow_fast_blocks);
2415 let conflict = manager
2416 .requested_signed_proposal
2417 .as_ref()
2418 .into_iter()
2419 .chain(&manager.requested_proposed)
2420 .any(|proposal| proposal.content.round == manager.current_round)
2421 || skip_fast;
2422 let round = if !conflict {
2423 manager.current_round
2424 } else if let Some(round) = manager
2425 .ownership
2426 .next_round(manager.current_round)
2427 .filter(|_| manager.current_round.is_multi_leader() || manager.current_round.is_fast())
2428 {
2429 round
2430 } else if let Some(timeout) = info.round_timeout() {
2431 return Ok(Either::Right(timeout));
2432 } else {
2433 return Err(Error::BlockProposalError(
2434 "Conflicting proposal in the current round",
2435 ));
2436 };
2437 let current_committee = self
2438 .local_committee()
2439 .await?
2440 .validators
2441 .values()
2442 .map(|v| (AccountOwner::from(v.account_public_key), v.votes))
2443 .collect();
2444 if manager.should_propose(identity, round, seed, ¤t_committee) {
2445 return Ok(Either::Left(round));
2446 }
2447 if let Some(timeout) = info.round_timeout() {
2448 return Ok(Either::Right(timeout));
2449 }
2450 Err(Error::BlockProposalError(
2451 "Not a leader in the current round",
2452 ))
2453 }
2454
2455 #[instrument(level = "trace")]
2462 pub async fn clear_pending_proposal(&self) {
2463 *self.proposal_mutex().lock().await = None;
2464 }
2465
2466 #[cfg(with_testing)]
2470 #[instrument(level = "trace")]
2471 pub async fn rotate_key_pair(
2472 &self,
2473 public_key: linera_base::crypto::AccountPublicKey,
2474 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2475 self.transfer_ownership(public_key.into()).await
2476 }
2477
2478 #[instrument(level = "trace")]
2480 pub async fn transfer_ownership(
2481 &self,
2482 new_owner: AccountOwner,
2483 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2484 self.execute_operation(SystemOperation::ChangeOwnership {
2485 super_owners: vec![new_owner],
2486 owners: Vec::new(),
2487 first_leader: None,
2488 multi_leader_rounds: 5,
2489 open_multi_leader_rounds: false,
2490 timeout_config: TimeoutConfig::default(),
2491 })
2492 .await
2493 }
2494
2495 #[instrument(level = "trace")]
2497 pub async fn share_ownership(
2498 &self,
2499 new_owner: AccountOwner,
2500 new_weight: u64,
2501 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2502 let ownership = self.prepare_chain().await?.manager.ownership;
2503 ensure!(
2504 ownership.is_active(),
2505 ChainError::InactiveChain(self.chain_id)
2506 );
2507 let mut owners = ownership.owners.into_iter().collect::<Vec<_>>();
2508 owners.extend(ownership.super_owners.into_iter().zip(iter::repeat(100)));
2509 owners.push((new_owner, new_weight));
2510 let operations = vec![Operation::system(SystemOperation::ChangeOwnership {
2511 super_owners: Vec::new(),
2512 owners,
2513 first_leader: ownership.first_leader,
2514 multi_leader_rounds: ownership.multi_leader_rounds,
2515 open_multi_leader_rounds: ownership.open_multi_leader_rounds,
2516 timeout_config: ownership.timeout_config,
2517 })];
2518 self.execute_block(operations, vec![]).await
2519 }
2520
2521 #[instrument(level = "trace")]
2523 pub async fn query_chain_ownership(&self) -> Result<ChainOwnership, Error> {
2524 Ok(self
2525 .client
2526 .local_node
2527 .chain_state_view(self.chain_id)
2528 .await?
2529 .execution_state
2530 .system
2531 .ownership
2532 .get()
2533 .await?
2534 .clone())
2535 }
2536
2537 #[instrument(level = "trace")]
2540 pub async fn change_ownership(
2541 &self,
2542 ownership: ChainOwnership,
2543 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2544 self.execute_operation(SystemOperation::ChangeOwnership {
2545 super_owners: ownership.super_owners.into_iter().collect(),
2546 owners: ownership.owners.into_iter().collect(),
2547 first_leader: ownership.first_leader,
2548 multi_leader_rounds: ownership.multi_leader_rounds,
2549 open_multi_leader_rounds: ownership.open_multi_leader_rounds,
2550 timeout_config: ownership.timeout_config.clone(),
2551 })
2552 .await
2553 }
2554
2555 #[instrument(level = "trace")]
2557 pub async fn query_application_permissions(&self) -> Result<ApplicationPermissions, Error> {
2558 Ok(self
2559 .client
2560 .local_node
2561 .chain_state_view(self.chain_id)
2562 .await?
2563 .execution_state
2564 .system
2565 .application_permissions
2566 .get()
2567 .await?
2568 .clone())
2569 }
2570
2571 #[instrument(level = "trace", skip(application_permissions))]
2573 pub async fn change_application_permissions(
2574 &self,
2575 application_permissions: ApplicationPermissions,
2576 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2577 self.execute_operation(SystemOperation::ChangeApplicationPermissions(
2578 application_permissions,
2579 ))
2580 .await
2581 }
2582
2583 #[instrument(level = "trace", skip(self))]
2585 pub async fn open_chain(
2586 &self,
2587 ownership: ChainOwnership,
2588 application_permissions: ApplicationPermissions,
2589 balance: Amount,
2590 ) -> Result<ClientOutcome<(ChainDescription, ConfirmedBlockCertificate)>, Error> {
2591 let mut has_key = false;
2593 for owner in ownership.all_owners() {
2594 if self.has_key_for(owner).await? {
2595 has_key = true;
2596 break;
2597 }
2598 }
2599 let config = OpenChainConfig {
2600 ownership,
2601 balance,
2602 application_permissions,
2603 };
2604 let operation = Operation::system(SystemOperation::OpenChain(config));
2605 let certificate = match self.execute_block(vec![operation], vec![]).await? {
2606 ClientOutcome::Committed(certificate) => certificate,
2607 ClientOutcome::Conflict(certificate) => {
2608 return Ok(ClientOutcome::Conflict(certificate));
2609 }
2610 ClientOutcome::WaitForTimeout(timeout) => {
2611 return Ok(ClientOutcome::WaitForTimeout(timeout));
2612 }
2613 };
2614 let chain_blob = certificate
2616 .block()
2617 .body
2618 .blobs
2619 .last()
2620 .and_then(|blobs| blobs.last())
2621 .ok_or_else(|| Error::InternalError("Failed to create a new chain"))?;
2622 let description = bcs::from_bytes::<ChainDescription>(chain_blob.bytes())?;
2623 if has_key {
2625 self.client
2626 .extend_chain_mode(description.id(), ListeningMode::FullChain);
2627 self.client
2628 .retry_pending_cross_chain_requests(self.chain_id)
2629 .await?;
2630 }
2631 Ok(ClientOutcome::Committed((description, certificate)))
2632 }
2633
2634 #[instrument(level = "trace")]
2638 pub async fn checkpoint(&self) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2639 self.execute_operation(SystemOperation::Checkpoint).await
2640 }
2641
2642 #[instrument(level = "trace")]
2645 pub async fn close_chain(
2646 &self,
2647 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2648 match self.execute_operation(SystemOperation::CloseChain).await {
2649 Ok(outcome) => Ok(outcome.map(Some)),
2650 Err(Error::LocalNodeError(LocalNodeError::WorkerError(WorkerError::ChainError(
2651 chain_error,
2652 )))) if matches!(*chain_error, ChainError::ClosedChain) => {
2653 Ok(ClientOutcome::Committed(None)) }
2655 Err(error) => Err(error),
2656 }
2657 }
2658
2659 #[cfg(not(target_arch = "wasm32"))]
2662 #[instrument(level = "trace", skip(contract, service, formats))]
2663 pub async fn publish_module(
2664 &self,
2665 contract: Bytecode,
2666 service: Bytecode,
2667 vm_runtime: VmRuntime,
2668 formats: Option<Vec<u8>>,
2669 ) -> Result<ClientOutcome<(ModuleId, ConfirmedBlockCertificate)>, Error> {
2670 let (blobs, module_id) =
2671 super::create_bytecode_blobs(contract, service, vm_runtime, formats).await;
2672 self.publish_module_blobs(blobs, module_id).await
2673 }
2674
2675 #[cfg(not(target_arch = "wasm32"))]
2677 #[instrument(level = "trace", skip(blobs, module_id))]
2678 pub async fn publish_module_blobs(
2679 &self,
2680 blobs: Vec<Blob>,
2681 module_id: ModuleId,
2682 ) -> Result<ClientOutcome<(ModuleId, ConfirmedBlockCertificate)>, Error> {
2683 self.execute_operations(
2684 vec![Operation::system(SystemOperation::PublishModule {
2685 module_id,
2686 })],
2687 blobs,
2688 )
2689 .await?
2690 .try_map(|certificate| Ok((module_id, certificate)))
2691 }
2692
2693 #[instrument(level = "trace", skip(bytes))]
2695 pub async fn publish_data_blobs(
2696 &self,
2697 bytes: Vec<Vec<u8>>,
2698 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2699 let blobs = bytes.into_iter().map(Blob::new_data);
2700 let publish_blob_operations = blobs
2701 .clone()
2702 .map(|blob| {
2703 Operation::system(SystemOperation::PublishDataBlob {
2704 blob_hash: blob.id().hash,
2705 })
2706 })
2707 .collect();
2708 self.execute_operations(publish_blob_operations, blobs.collect())
2709 .await
2710 }
2711
2712 #[instrument(level = "trace", skip(bytes))]
2714 pub async fn publish_data_blob(
2715 &self,
2716 bytes: Vec<u8>,
2717 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2718 self.publish_data_blobs(vec![bytes]).await
2719 }
2720
2721 #[instrument(
2723 level = "trace",
2724 skip(self, parameters, instantiation_argument, required_application_ids)
2725 )]
2726 pub async fn create_application<
2727 A: Abi,
2728 Parameters: Serialize,
2729 InstantiationArgument: Serialize,
2730 >(
2731 &self,
2732 module_id: ModuleId<A, Parameters, InstantiationArgument>,
2733 parameters: &Parameters,
2734 instantiation_argument: &InstantiationArgument,
2735 required_application_ids: Vec<ApplicationId>,
2736 ) -> Result<ClientOutcome<(ApplicationId<A>, ConfirmedBlockCertificate)>, Error> {
2737 let instantiation_argument = serde_json::to_vec(instantiation_argument)?;
2738 let parameters = serde_json::to_vec(parameters)?;
2739 Ok(self
2740 .create_application_untyped(
2741 module_id.forget_abi(),
2742 parameters,
2743 instantiation_argument,
2744 required_application_ids,
2745 )
2746 .await?
2747 .map(|(app_id, cert)| (app_id.with_abi(), cert)))
2748 }
2749
2750 #[instrument(
2752 level = "trace",
2753 skip(
2754 self,
2755 module_id,
2756 parameters,
2757 instantiation_argument,
2758 required_application_ids
2759 )
2760 )]
2761 pub async fn create_application_untyped(
2762 &self,
2763 module_id: ModuleId,
2764 parameters: Vec<u8>,
2765 instantiation_argument: Vec<u8>,
2766 required_application_ids: Vec<ApplicationId>,
2767 ) -> Result<ClientOutcome<(ApplicationId, ConfirmedBlockCertificate)>, Error> {
2768 self.execute_operation(SystemOperation::CreateApplication {
2769 module_id,
2770 parameters,
2771 instantiation_argument,
2772 required_application_ids,
2773 })
2774 .await?
2775 .try_map(|certificate| {
2776 let mut creation = certificate
2778 .block()
2779 .created_blob_ids()
2780 .into_iter()
2781 .filter(|blob_id| blob_id.blob_type == BlobType::ApplicationDescription)
2782 .collect::<Vec<_>>();
2783 if creation.len() > 1 {
2784 return Err(Error::InternalError(
2785 "Unexpected number of application descriptions published",
2786 ));
2787 }
2788 let blob_id = creation.pop().ok_or(Error::InternalError(
2789 "ApplicationDescription blob not found.",
2790 ))?;
2791 let id = ApplicationId::new(blob_id.hash);
2792 Ok((id, certificate))
2793 })
2794 }
2795
2796 #[instrument(level = "trace", skip(committee))]
2798 pub async fn stage_new_committee(
2799 &self,
2800 committee: Committee,
2801 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2802 let blob = Blob::new(BlobContent::new_committee(bcs::to_bytes(&committee)?));
2803 let blob_hash = blob.id().hash;
2804 match self
2805 .execute_operations(
2806 vec![Operation::system(SystemOperation::Admin(
2807 AdminOperation::PublishCommitteeBlob { blob_hash },
2808 ))],
2809 vec![blob],
2810 )
2811 .await?
2812 {
2813 ClientOutcome::Committed(_) => {}
2814 outcome @ ClientOutcome::WaitForTimeout(_) | outcome @ ClientOutcome::Conflict(_) => {
2815 return Ok(outcome)
2816 }
2817 }
2818 let epoch = self.chain_info().await?.epoch.try_add_one()?;
2819 self.execute_operation(SystemOperation::Admin(AdminOperation::CreateCommittee {
2820 epoch,
2821 blob_hash,
2822 }))
2823 .await
2824 }
2825
2826 #[instrument(level = "trace")]
2832 pub async fn process_inbox(
2833 &self,
2834 ) -> Result<(Vec<ConfirmedBlockCertificate>, Option<RoundTimeout>), Error> {
2835 self.prepare_chain().await?;
2836 self.process_inbox_without_prepare().await
2837 }
2838
2839 #[instrument(level = "trace")]
2845 pub async fn process_inbox_without_prepare(
2846 &self,
2847 ) -> Result<(Vec<ConfirmedBlockCertificate>, Option<RoundTimeout>), Error> {
2848 #[cfg(with_metrics)]
2849 let _latency = super::metrics::PROCESS_INBOX_WITHOUT_PREPARE_LATENCY.measure_latency();
2850
2851 let mut certificates = Vec::new();
2852 loop {
2853 match self.execute_block(vec![], vec![]).await {
2857 Ok(ClientOutcome::Committed(certificate)) => certificates.push(certificate),
2858 Ok(ClientOutcome::Conflict(certificate)) => certificates.push(*certificate),
2859 Ok(ClientOutcome::WaitForTimeout(timeout)) => {
2860 return Ok((certificates, Some(timeout)));
2861 }
2862 Err(Error::LocalNodeError(LocalNodeError::WorkerError(
2864 WorkerError::ChainError(chain_error),
2865 ))) if matches!(*chain_error, ChainError::EmptyBlock) => {
2866 return Ok((certificates, None));
2867 }
2868 Err(error) => return Err(error),
2869 };
2870 }
2871 }
2872
2873 async fn next_epoch_change(&self) -> Result<Option<Operation>, Error> {
2878 let next_epoch = self.chain_info().await?.epoch.try_add_one()?;
2879 if !self
2880 .has_admin_event(EPOCH_STREAM_NAME, next_epoch.0)
2881 .await?
2882 {
2883 return Ok(None);
2884 }
2885 Ok(Some(Operation::system(SystemOperation::ProcessNewEpoch(
2886 next_epoch,
2887 ))))
2888 }
2889
2890 async fn has_admin_event(&self, stream_name: &[u8], index: u32) -> Result<bool, Error> {
2893 let event_id = EventId {
2894 chain_id: self.client.admin_chain_id,
2895 stream_id: StreamId::system(stream_name),
2896 index,
2897 };
2898 Ok(self
2899 .client
2900 .storage_client()
2901 .read_event(event_id)
2902 .await?
2903 .is_some())
2904 }
2905
2906 pub async fn events_from_index(
2908 &self,
2909 stream_id: StreamId,
2910 start_index: u32,
2911 ) -> Result<Vec<IndexAndEvent>, Error> {
2912 Ok(self
2913 .client
2914 .storage_client()
2915 .read_events_from_index(&self.chain_id, &stream_id, start_index)
2916 .await?)
2917 }
2918
2919 #[instrument(level = "trace")]
2923 pub async fn revoke_epochs(
2924 &self,
2925 revoked_epoch: Epoch,
2926 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2927 self.prepare_chain().await?;
2928 let current_epoch = self.chain_info().await?.epoch;
2929 ensure!(
2930 revoked_epoch < current_epoch,
2931 Error::CannotRevokeCurrentEpoch(current_epoch)
2932 );
2933 let mut operations = Vec::new();
2934 for epoch_index in 0..=revoked_epoch.0 {
2935 let epoch = Epoch(epoch_index);
2936 if self
2937 .has_admin_event(REMOVED_EPOCH_STREAM_NAME, epoch.0)
2938 .await?
2939 {
2940 continue;
2941 }
2942 operations.push(Operation::system(SystemOperation::Admin(
2943 AdminOperation::RemoveCommittee { epoch },
2944 )));
2945 }
2946 ensure!(!operations.is_empty(), Error::EpochAlreadyRevoked);
2947 self.execute_operations(operations, vec![]).await
2948 }
2949
2950 #[cfg(with_testing)]
2954 #[instrument(level = "trace")]
2955 pub async fn transfer_to_account_unsafe_unconfirmed(
2956 &self,
2957 owner: AccountOwner,
2958 amount: Amount,
2959 recipient: Account,
2960 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2961 self.execute_operation(SystemOperation::Transfer {
2962 owner,
2963 recipient,
2964 amount,
2965 })
2966 .await
2967 }
2968
2969 #[instrument(level = "trace", skip(hash))]
2971 pub async fn read_confirmed_block(
2972 &self,
2973 hash: CryptoHash,
2974 ) -> Result<Arc<ConfirmedBlock>, Error> {
2975 self.client
2976 .storage_client()
2977 .read_confirmed_block(hash)
2978 .await?
2979 .ok_or(Error::MissingConfirmedBlock(hash))
2980 .map(|b| b.into_std())
2981 }
2982
2983 #[instrument(level = "trace", skip(hash))]
2985 pub async fn read_certificate(
2986 &self,
2987 hash: CryptoHash,
2988 ) -> Result<CacheArc<ConfirmedBlockCertificate>, Error> {
2989 self.client
2990 .storage_client()
2991 .read_certificate(hash)
2992 .await?
2993 .ok_or(Error::ReadCertificatesError(vec![hash]))
2994 }
2995
2996 #[instrument(level = "trace")]
2998 pub async fn retry_pending_outgoing_messages(&self) -> Result<(), Error> {
2999 self.client
3000 .retry_pending_cross_chain_requests(self.chain_id)
3001 .await?;
3002 Ok(())
3003 }
3004
3005 #[instrument(level = "trace", skip(local_node))]
3006 async fn maybe_local_chain_info(
3007 &self,
3008 chain_id: ChainId,
3009 local_node: &LocalNodeClient<Env::Storage>,
3010 ) -> Result<Option<Box<ChainInfo>>, Error> {
3011 match local_node.chain_info(chain_id).await {
3012 Ok(info) => Ok(Some(info)),
3013 Err(LocalNodeError::BlobsNotFound(_) | LocalNodeError::InactiveChain(_)) => Ok(None),
3014 Err(err) => Err(err.into()),
3015 }
3016 }
3017
3018 #[instrument(level = "trace", skip(chain_id, local_node))]
3019 async fn local_next_block_height(
3020 &self,
3021 chain_id: ChainId,
3022 local_node: &LocalNodeClient<Env::Storage>,
3023 ) -> Result<BlockHeight, Error> {
3024 Ok(self
3025 .maybe_local_chain_info(chain_id, local_node)
3026 .await?
3027 .map_or(BlockHeight::ZERO, |info| info.next_block_height))
3028 }
3029
3030 #[instrument(level = "trace")]
3033 async fn local_next_height_to_receive(&self, origin: ChainId) -> Result<BlockHeight, Error> {
3034 Ok(self
3035 .client
3036 .local_node
3037 .get_inbox_next_height(self.chain_id, origin)
3038 .await?)
3039 }
3040
3041 #[instrument(level = "trace", skip(remote_node, local_node, notification))]
3042 async fn process_notification(
3043 &self,
3044 remote_node: RemoteNode<Env::ValidatorNode>,
3045 local_node: LocalNodeClient<Env::Storage>,
3046 notification: Notification,
3047 ) -> Result<(), Error> {
3048 let listening_mode = self.client.chain_mode(notification.chain_id);
3049 let relevant = listening_mode
3050 .as_ref()
3051 .is_some_and(|mode| mode.is_relevant(¬ification.reason));
3052 if !relevant {
3053 tracing::trace!(
3054 chain_id = %notification.chain_id,
3055 reason = ?notification.reason,
3056 ?listening_mode,
3057 "Ignoring notification due to listening mode"
3058 );
3059 return Ok(());
3060 }
3061 match notification.reason {
3062 Reason::NewIncomingBundle { origin, height } => {
3063 if self.options.message_policy.ignores_origin(&origin) {
3064 trace!(
3065 chain_id = %self.chain_id,
3066 %origin,
3067 %height,
3068 "Skipping NewIncomingBundle notification: origin filtered by message_policy"
3069 );
3070 return Ok(());
3071 }
3072 if self.local_next_height_to_receive(origin).await? > height {
3073 debug!(
3074 chain_id = %self.chain_id,
3075 "Accepting redundant notification for new message"
3076 );
3077 return Ok(());
3078 }
3079 self.client
3080 .download_sender_block_with_sending_ancestors(
3081 self.chain_id,
3082 origin,
3083 height,
3084 &remote_node,
3085 )
3086 .await?;
3087 if self.local_next_height_to_receive(origin).await? <= height {
3088 info!(
3089 chain_id = %self.chain_id,
3090 "NewIncomingBundle: Fail to synchronize new message after notification"
3091 );
3092 }
3093 }
3094 Reason::NewBlock { height, .. } => {
3095 let chain_id = notification.chain_id;
3096 let local_height = self.local_next_block_height(chain_id, &local_node).await?;
3097 if local_height > height {
3098 debug!(
3099 chain_id = %self.chain_id,
3100 "Accepting redundant notification for new block"
3101 );
3102 return Ok(());
3103 }
3104 self.client
3107 .synchronize_chain_state_from(&remote_node, chain_id)
3108 .await?;
3109 if self.local_next_block_height(chain_id, &local_node).await? <= height {
3110 error!("NewBlock: Fail to synchronize new block after notification");
3111 }
3112 trace!(
3113 chain_id = %self.chain_id,
3114 %height,
3115 "NewBlock: processed notification",
3116 );
3117 }
3118 Reason::NewEvents {
3119 height, block_hash, ..
3120 } => {
3121 let chain_id = notification.chain_id;
3122 let local_height = self.local_next_block_height(chain_id, &local_node).await?;
3123 if local_height > height {
3124 debug!(
3125 chain_id = %self.chain_id,
3126 "Accepting redundant notification for new events"
3127 );
3128 return Ok(());
3129 }
3130 trace!(
3131 chain_id = %self.chain_id,
3132 %height,
3133 "NewEvents: processing notification"
3134 );
3135 let relevant_streams = match self.listening_mode() {
3138 Some(ListeningMode::EventsOnly(subscribed)) => subscribed,
3139 _ => unreachable!(),
3142 };
3143 self.client
3144 .download_event_bearing_blocks(
3145 self.chain_id,
3146 BTreeSet::from([(height, block_hash)]),
3147 local_height,
3148 &relevant_streams,
3149 &remote_node,
3150 )
3151 .await?;
3152 }
3153 Reason::NewRound { height, round } => {
3154 let chain_id = notification.chain_id;
3155 if let Some(info) = self.maybe_local_chain_info(chain_id, &local_node).await? {
3156 if (info.next_block_height, info.manager.current_round) >= (height, round) {
3157 debug!(
3158 chain_id = %self.chain_id,
3159 "Accepting redundant notification for new round"
3160 );
3161 return Ok(());
3162 }
3163 }
3164 self.client
3165 .synchronize_chain_state_from(&remote_node, chain_id)
3166 .await?;
3167 let Some(info) = self.maybe_local_chain_info(chain_id, &local_node).await? else {
3168 error!(
3169 chain_id = %self.chain_id,
3170 "NewRound: Fail to read local chain info for {chain_id}"
3171 );
3172 return Ok(());
3173 };
3174 if (info.next_block_height, info.manager.current_round) < (height, round) {
3175 info!(
3176 chain_id = %self.chain_id,
3177 "NewRound: Fail to synchronize new block after notification"
3178 );
3179 }
3180 }
3181 Reason::BlockExecuted { .. } => {
3182 }
3184 }
3185 Ok(())
3186 }
3187
3188 pub fn is_tracked(&self) -> bool {
3190 self.client.is_tracked(self.chain_id)
3191 }
3192
3193 pub fn listening_mode(&self) -> Option<ListeningMode> {
3195 self.client.chain_mode(self.chain_id)
3196 }
3197
3198 #[instrument(level = "trace", fields(chain_id = ?self.chain_id))]
3203 pub async fn listen(
3204 &self,
3205 ) -> Result<(impl Future<Output = ()>, AbortOnDrop, NotificationStream), Error> {
3206 use future::FutureExt as _;
3207
3208 async fn await_while_polling<F: FusedFuture>(
3209 future: F,
3210 background_work: impl FusedStream<Item = ()>,
3211 ) -> F::Output {
3212 tokio::pin!(future);
3213 tokio::pin!(background_work);
3214 loop {
3215 futures::select! {
3216 _ = background_work.next() => (),
3217 result = future => return result,
3218 }
3219 }
3220 }
3221
3222 let mut senders = HashMap::new();
3223 let mut circuit_breakers: HashMap<ValidatorPublicKey, CircuitBreakerState> = HashMap::new();
3224 let notifications = self.subscribe()?;
3225 let (abortable_notifications, abort) = stream::abortable(self.subscribe()?);
3226
3227 let mut process_notifications = FuturesUnordered::new();
3234
3235 match self
3236 .update_notification_streams(&mut senders, &mut circuit_breakers)
3237 .await
3238 {
3239 Ok(handler) => process_notifications.push(handler),
3240 Err(error) => error!("Failed to update committee: {error}"),
3241 };
3242
3243 let this = self.clone();
3244 let update_streams = async move {
3245 let mut abortable_notifications = abortable_notifications.fuse();
3246
3247 while let Some(notification) =
3248 await_while_polling(abortable_notifications.next(), &mut process_notifications)
3249 .await
3250 {
3251 if let Reason::NewBlock { .. } = notification.reason {
3252 match Box::pin(await_while_polling(
3253 this.update_notification_streams(&mut senders, &mut circuit_breakers)
3254 .fuse(),
3255 &mut process_notifications,
3256 ))
3257 .await
3258 {
3259 Ok(handler) => process_notifications.push(handler),
3260 Err(error) => error!("Failed to update committee: {error}"),
3261 }
3262 }
3263 }
3264
3265 for abort in senders.into_values() {
3266 abort.abort();
3267 }
3268
3269 let () = process_notifications.collect().await;
3270 }
3271 .in_current_span();
3272
3273 Ok((update_streams, AbortOnDrop(abort), notifications))
3274 }
3275
3276 #[instrument(level = "trace", skip(senders, circuit_breakers))]
3277 async fn update_notification_streams(
3278 &self,
3279 senders: &mut HashMap<ValidatorPublicKey, AbortHandle>,
3280 circuit_breakers: &mut HashMap<ValidatorPublicKey, CircuitBreakerState>,
3281 ) -> Result<impl Future<Output = ()>, Error> {
3282 let initial_probe_interval = self
3283 .options
3284 .notification_circuit_breaker_initial_probe_interval;
3285 let max_probe_interval = self.options.notification_circuit_breaker_max_probe_interval;
3286 let now = self.storage_client().clock().current_time();
3289 let (nodes, local_node) = {
3290 let committee = if self
3294 .listening_mode()
3295 .is_some_and(|m| m.should_sync_chain_state())
3296 {
3297 self.local_committee().await?
3298 } else {
3299 self.client.admin_committee().await?.1
3300 };
3301 let nodes = self
3302 .client
3303 .validator_node_provider()
3304 .make_nodes(&committee)?
3305 .collect::<HashMap<_, _>>();
3306 (nodes, self.client.local_node.clone())
3307 };
3308 for (validator, abort) in senders.iter() {
3310 if abort.is_aborted() && nodes.contains_key(validator) {
3311 if let Some(state) = circuit_breakers.get_mut(validator) {
3312 state.probe_interval = (state.probe_interval * 2).min(max_probe_interval);
3314 state.next_probe_at =
3315 now.saturating_add(TimeDelta::from_duration(state.probe_interval));
3316 warn!(
3317 %validator,
3318 chain_id = %self.chain_id,
3319 next_probe_in = ?state.probe_interval,
3320 "Validator still unhealthy after probe; increasing probe interval"
3321 );
3322 } else {
3323 circuit_breakers.insert(
3325 *validator,
3326 CircuitBreakerState {
3327 next_probe_at: now
3328 .saturating_add(TimeDelta::from_duration(initial_probe_interval)),
3329 probe_interval: initial_probe_interval,
3330 },
3331 );
3332 error!(
3333 %validator,
3334 chain_id = %self.chain_id,
3335 next_probe_in = ?initial_probe_interval,
3336 "Validator notification stream ended; entering circuit breaker"
3337 );
3338 }
3339 } else if !abort.is_aborted() && circuit_breakers.contains_key(validator) {
3340 info!(
3342 %validator,
3343 chain_id = %self.chain_id,
3344 "Validator recovered from circuit breaker"
3345 );
3346 circuit_breakers.remove(validator);
3347 }
3348 }
3349
3350 senders.retain(|validator, abort| {
3351 if !nodes.contains_key(validator) {
3352 abort.abort();
3353 }
3354 !abort.is_aborted()
3355 });
3356 circuit_breakers.retain(|validator, _| nodes.contains_key(validator));
3357
3358 let validator_tasks = FuturesUnordered::new();
3359 for (public_key, node) in nodes {
3360 let hash_map::Entry::Vacant(entry) = senders.entry(public_key) else {
3361 continue;
3362 };
3363
3364 if let Some(state) = circuit_breakers.get(&public_key) {
3366 if now < state.next_probe_at {
3367 continue;
3368 }
3369 debug!(
3370 validator = %public_key,
3371 chain_id = %self.chain_id,
3372 "Probing unhealthy validator"
3373 );
3374 }
3375
3376 let address = node.address();
3377 let this = self.clone();
3378 let listening_mode_for_sync = self.listening_mode();
3379 let stream = stream::once({
3380 let node = node.clone();
3381 async move {
3382 let stream = node.subscribe(vec![this.chain_id]).await?;
3383 let remote_node = RemoteNode { public_key, node };
3386 if listening_mode_for_sync
3387 .as_ref()
3388 .is_some_and(|mode| mode.should_sync_chain_state())
3389 {
3390 this.client
3391 .synchronize_chain_state_from(&remote_node, this.chain_id)
3392 .await?;
3393 } else {
3394 if let Some(ListeningMode::EventsOnly(subscribed)) =
3398 listening_mode_for_sync.as_ref()
3399 {
3400 if let Err(error) = this
3401 .client
3402 .sync_events_from_node(this.chain_id, subscribed, &remote_node)
3403 .await
3404 {
3405 debug!(
3406 chain_id = %this.chain_id,
3407 %error,
3408 "Failed initial sparse sync for EventsOnly chain"
3409 );
3410 }
3411 }
3412 }
3413 Ok::<_, Error>(stream)
3414 }
3415 })
3416 .filter_map(move |result| {
3417 let address = address.clone();
3418 async move {
3419 if let Err(error) = &result {
3420 info!(?error, address, "could not connect to validator");
3421 } else {
3422 debug!(address, "connected to validator");
3423 }
3424 result.ok()
3425 }
3426 })
3427 .flatten();
3428 let (stream, abort) = stream::abortable(stream);
3429 let mut stream = Box::pin(stream);
3430 let abort_on_exit = abort.clone();
3431 let this = self.clone();
3432 let local_node = local_node.clone();
3433 let remote_node = RemoteNode { public_key, node };
3434 validator_tasks.push(async move {
3435 while let Some(notification) = stream.next().await {
3436 if let Err(error) = this
3437 .process_notification(
3438 remote_node.clone(),
3439 local_node.clone(),
3440 notification.clone(),
3441 )
3442 .await
3443 {
3444 tracing::info!(
3445 chain_id = %this.chain_id,
3446 address = remote_node.address(),
3447 ?notification,
3448 %error,
3449 "failed to process notification",
3450 );
3451 }
3452 }
3453 warn!(
3454 chain_id = %this.chain_id,
3455 address = remote_node.address(),
3456 "Validator notification stream ended"
3457 );
3458 abort_on_exit.abort();
3459 });
3460 entry.insert(abort);
3461 }
3462 Ok(validator_tasks.collect())
3463 }
3464
3465 #[instrument(level = "trace", skip(node))]
3471 pub async fn sync_validator(
3472 &self,
3473 public_key: ValidatorPublicKey,
3474 node: Env::ValidatorNode,
3475 ) -> Result<(), Error> {
3476 let local_next_block_height = self.chain_info().await?.next_block_height;
3477 let mut updater = ValidatorUpdater {
3478 remote_node: RemoteNode { public_key, node },
3479 client: self.client.clone(),
3480 admin_chain_id: self.client.admin_chain_id,
3481 };
3482 updater
3483 .send_chain_information(
3484 self.chain_id,
3485 local_next_block_height,
3486 CrossChainMessageDelivery::NonBlocking,
3487 None,
3488 )
3489 .await
3490 }
3491}
3492
3493#[cfg(with_testing)]
3494impl<Env: Environment> ChainClient<Env> {
3495 pub async fn process_notification_from(
3497 &self,
3498 notification: Notification,
3499 validator: (ValidatorPublicKey, &str),
3500 ) {
3501 let mut node_list = self
3502 .client
3503 .validator_node_provider()
3504 .make_nodes_from_list(vec![validator])
3505 .unwrap();
3506 let (public_key, node) = node_list.next().unwrap();
3507 let remote_node = RemoteNode { node, public_key };
3508 let local_node = self.client.local_node.clone();
3509 self.process_notification(remote_node, local_node, notification)
3510 .await
3511 .unwrap();
3512 }
3513}
3514
3515#[cfg(test)]
3516mod tests {
3517 use super::{Error, LocalNodeError};
3518
3519 #[test]
3520 fn error_type_delegates_to_local_node_error() {
3521 assert_eq!(
3522 Error::LocalNodeError(LocalNodeError::InvalidChainInfoResponse).error_type(),
3523 "LocalNodeError::InvalidChainInfoResponse"
3524 );
3525 }
3526
3527 #[test]
3528 fn error_type_falls_back_to_chain_client_variant() {
3529 assert_eq!(
3530 Error::WalletSynchronizationError.error_type(),
3531 "ChainClientError::WalletSynchronizationError"
3532 );
3533 }
3534}