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(crate) 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
65#[cfg(not(target_arch = "wasm32"))]
66use super::create_bytecode_blobs;
67use super::{
68 received_log::ReceivedLogs, validator_trackers::ValidatorTrackers, AbortOnDrop, Client,
69 ListeningMode, PendingProposal, TimingType,
70};
71use crate::{
72 data_types::{ChainInfo, ChainInfoQuery, ClientOutcome, RoundTimeout},
73 environment::Environment,
74 local_node::{LocalNodeClient, LocalNodeError},
75 node::{
76 CrossChainMessageDelivery, NodeError, NotificationStream, ValidatorNode,
77 ValidatorNodeProvider as _,
78 },
79 remote_node::RemoteNode,
80 updater::{communicate_with_quorum, CommunicateAction, CommunicationError},
81 worker::{Notification, Reason, WorkerError},
82};
83
84#[derive(Debug, Clone)]
86pub struct Options {
87 pub max_pending_message_bundles: usize,
89 pub max_block_limit_errors: u32,
94 pub max_new_events_per_block: usize,
96 pub staging_bundles_time_budget: Option<Duration>,
99 pub message_policy: MessagePolicy,
101 pub priority_bundle_origins: HashSet<ChainId>,
103 pub cross_chain_message_delivery: CrossChainMessageDelivery,
105 pub quorum_grace_period: f64,
108 pub blob_download_hedge_delay: Duration,
110 pub certificate_batch_download_hedge_delay: Duration,
112 pub certificate_download_batch_size: u64,
115 pub certificate_upload_batch_size: u64,
118 pub sender_certificate_download_batch_size: usize,
121 pub max_concurrent_batch_downloads: usize,
123 pub max_joined_tasks: usize,
125 pub allow_fast_blocks: bool,
128 pub notification_circuit_breaker_initial_probe_interval: Duration,
132 pub notification_circuit_breaker_max_probe_interval: Duration,
135 pub max_event_stream_queries: usize,
138}
139
140struct CircuitBreakerState {
141 next_probe_at: Timestamp,
142 probe_interval: Duration,
143}
144
145#[derive(Clone, Copy, Debug, PartialEq, Eq)]
148struct ConsensusStateSnapshot {
149 next_block_height: BlockHeight,
150 current_round: Round,
151 lock_round: Option<Round>,
152 timeout_round: Option<Round>,
153}
154
155#[cfg(with_testing)]
156impl Options {
157 pub fn test_default() -> Self {
159 use super::{
160 DEFAULT_CERTIFICATE_DOWNLOAD_BATCH_SIZE, DEFAULT_CERTIFICATE_UPLOAD_BATCH_SIZE,
161 DEFAULT_MAX_CONCURRENT_BATCH_DOWNLOADS, DEFAULT_MAX_EVENT_STREAM_QUERIES,
162 DEFAULT_SENDER_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
163 };
164 use crate::DEFAULT_QUORUM_GRACE_PERIOD;
165
166 Options {
167 max_pending_message_bundles: 10,
168 max_block_limit_errors: 3,
169 max_new_events_per_block: 10,
170 staging_bundles_time_budget: None,
171 message_policy: MessagePolicy::default(),
172 priority_bundle_origins: HashSet::new(),
173 cross_chain_message_delivery: CrossChainMessageDelivery::NonBlocking,
174 quorum_grace_period: DEFAULT_QUORUM_GRACE_PERIOD,
175 blob_download_hedge_delay: Duration::from_secs(1),
176 certificate_batch_download_hedge_delay: Duration::from_secs(1),
177 certificate_download_batch_size: DEFAULT_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
178 certificate_upload_batch_size: DEFAULT_CERTIFICATE_UPLOAD_BATCH_SIZE,
179 sender_certificate_download_batch_size: DEFAULT_SENDER_CERTIFICATE_DOWNLOAD_BATCH_SIZE,
180 max_concurrent_batch_downloads: DEFAULT_MAX_CONCURRENT_BATCH_DOWNLOADS,
181 max_joined_tasks: 100,
182 allow_fast_blocks: false,
183 notification_circuit_breaker_initial_probe_interval: Duration::from_secs(300),
184 notification_circuit_breaker_max_probe_interval: Duration::from_secs(3600),
185 max_event_stream_queries: DEFAULT_MAX_EVENT_STREAM_QUERIES,
186 }
187 }
188}
189
190impl Options {
191 pub fn bundle_execution_policy(&self) -> BundleExecutionPolicy {
193 BundleExecutionPolicy {
194 on_failure: BundleFailurePolicy::AutoRetry {
195 max_failures: self.max_block_limit_errors,
196 never_reject_application_ids: Arc::new(
197 self.message_policy.never_reject_application_ids.clone(),
198 ),
199 },
200 time_budget: self.staging_bundles_time_budget,
201 }
202 }
203}
204
205#[derive(Debug)]
211pub struct ChainClient<Env: Environment> {
212 #[debug(skip)]
214 pub(crate) client: Arc<Client<Env>>,
215 chain_id: ChainId,
217 #[debug(skip)]
219 options: Options,
220 preferred_owner: Option<AccountOwner>,
223 initial_next_block_height: BlockHeight,
225 initial_block_hash: Option<CryptoHash>,
227 timing_sender: Option<mpsc::UnboundedSender<(u64, TimingType)>>,
229 skipped_origins: Arc<papaya::HashSet<ChainId>>,
232}
233
234impl<Env: Environment> Clone for ChainClient<Env> {
235 fn clone(&self) -> Self {
236 Self {
237 client: self.client.clone(),
238 chain_id: self.chain_id,
239 options: self.options.clone(),
240 preferred_owner: self.preferred_owner,
241 initial_next_block_height: self.initial_next_block_height,
242 initial_block_hash: self.initial_block_hash,
243 timing_sender: self.timing_sender.clone(),
244 skipped_origins: self.skipped_origins.clone(),
245 }
246 }
247}
248
249#[derive(Debug, Error, strum::IntoStaticStr)]
251#[allow(missing_docs)]
252pub enum Error {
253 #[error("Local node operation failed: {0}")]
254 LocalNodeError(#[from] LocalNodeError),
255
256 #[error("Remote node operation failed: {0}")]
257 RemoteNodeError(#[from] NodeError),
258
259 #[error("The local node is lagging behind a validator on chain {chain_id}: {error}")]
262 LocalNodeLagging {
263 chain_id: ChainId,
264 error: Box<NodeError>,
265 },
266
267 #[error(transparent)]
268 ArithmeticError(#[from] ArithmeticError),
269
270 #[error("Missing certificates: {0:?}")]
271 ReadCertificatesError(Vec<CryptoHash>),
272
273 #[error("Missing confirmed block: {0:?}")]
274 MissingConfirmedBlock(CryptoHash),
275
276 #[error("JSON (de)serialization error: {0}")]
277 JsonError(#[from] serde_json::Error),
278
279 #[error("Chain operation failed: {0}")]
280 ChainError(#[from] ChainError),
281
282 #[error(transparent)]
283 CommunicationError(#[from] CommunicationError<NodeError>),
284
285 #[error("Internal error within chain client: {0}")]
286 InternalError(&'static str),
287
288 #[error(
289 "Cannot accept a certificate from an unknown committee in the future. \
290 Please synchronize the local view of the admin chain"
291 )]
292 CommitteeSynchronizationError,
293
294 #[error("The local node is behind the trusted state in wallet and needs synchronization with validators")]
295 WalletSynchronizationError,
296
297 #[error("The state of the client is incompatible with the proposed block: {0}")]
298 BlockProposalError(&'static str),
299
300 #[error(
301 "Cannot accept a certificate from a committee that was retired. \
302 Try a newer certificate from the same origin"
303 )]
304 CommitteeDeprecationError,
305
306 #[error("Protocol error within chain client: {0}")]
307 ProtocolError(&'static str),
308
309 #[error("Signer doesn't have key to sign for chain {0}")]
310 CannotFindKeyForChain(ChainId),
311
312 #[error("client is not configured to propose on chain {0}")]
313 NoAccountKeyConfigured(ChainId),
314
315 #[error("The chain client isn't owner on chain {0}")]
316 NotAnOwner(ChainId),
317
318 #[error(transparent)]
319 ViewError(#[from] ViewError),
320
321 #[error(
322 "Failed to download certificates and update local node to the next height \
323 {target_next_block_height} of chain {chain_id}"
324 )]
325 CannotDownloadCertificates {
326 chain_id: ChainId,
327 target_next_block_height: BlockHeight,
328 },
329
330 #[error("No validator provided a usable certificate registering blob {0}")]
331 CannotDownloadBlob(BlobId),
332
333 #[error(transparent)]
334 BcsError(#[from] bcs::Error),
335
336 #[error(
337 "Unexpected quorum: validators voted for block hash {hash} in {round}, \
338 expected block hash {expected_hash} in {expected_round}"
339 )]
340 UnexpectedQuorum {
341 hash: CryptoHash,
342 round: Round,
343 expected_hash: CryptoHash,
344 expected_round: Round,
345 },
346
347 #[error("signer error: {0:?}")]
348 Signer(#[source] Box<dyn signer::Error>),
349
350 #[error("Cannot revoke the current epoch {0}")]
351 CannotRevokeCurrentEpoch(Epoch),
352
353 #[error("Epoch is already revoked")]
354 EpochAlreadyRevoked,
355
356 #[error("Failed to download missing sender blocks from chain {chain_id} at height {height}")]
357 CannotDownloadMissingSenderBlock {
358 chain_id: ChainId,
359 height: BlockHeight,
360 },
361
362 #[error(
363 "A different block was already committed at this height. \
364 The committed certificate hash is {0}"
365 )]
366 Conflict(CryptoHash),
367}
368
369impl From<Infallible> for Error {
370 fn from(infallible: Infallible) -> Self {
371 match infallible {}
372 }
373}
374
375impl Error {
376 pub fn signer_failure(err: impl signer::Error + 'static) -> Self {
378 Self::Signer(Box::new(err))
379 }
380
381 pub fn error_type(&self) -> String {
385 match self {
386 Error::LocalNodeError(local_node_error) => local_node_error.error_type(),
387 Error::ChainError(chain_error) => chain_error.error_type(),
388 other => {
389 let variant: &'static str = other.into();
390 format!("ChainClientError::{variant}")
391 }
392 }
393 }
394}
395
396impl<Env: Environment> ChainClient<Env> {
397 #[instrument(level = "trace", skip_all, fields(chain_id, next_block_height))]
398 pub(crate) fn new(
399 client: Arc<Client<Env>>,
400 chain_id: ChainId,
401 options: Options,
402 initial_block_hash: Option<CryptoHash>,
403 initial_next_block_height: BlockHeight,
404 preferred_owner: Option<AccountOwner>,
405 timing_sender: Option<mpsc::UnboundedSender<(u64, TimingType)>>,
406 ) -> Self {
407 ChainClient {
408 client,
409 chain_id,
410 options,
411 preferred_owner,
412 initial_block_hash,
413 initial_next_block_height,
414 timing_sender,
415 skipped_origins: Arc::new(papaya::HashSet::new()),
416 }
417 }
418
419 #[instrument(level = "trace", skip(self))]
421 pub fn is_follow_only(&self) -> bool {
422 self.client
423 .chain_mode(self.chain_id)
424 .is_none_or(|mode| mode.is_follow_only())
425 }
426
427 #[instrument(level = "trace", skip(self))]
431 fn proposal_mutex(&self) -> Arc<tokio::sync::Mutex<Option<PendingProposal>>> {
432 self.client
433 .chains
434 .pin()
435 .get(&self.chain_id)
436 .expect("Chain client constructed for invalid chain")
437 .proposal_mutex()
438 }
439
440 #[instrument(level = "trace", skip(self))]
442 pub async fn pending_proposal(&self) -> Option<PendingProposal> {
443 self.proposal_mutex().lock().await.clone()
444 }
445
446 #[instrument(level = "trace", skip(self))]
448 pub fn signer(&self) -> &impl Signer {
449 self.client.signer()
450 }
451
452 pub async fn has_key_for(&self, owner: &AccountOwner) -> Result<bool, Error> {
454 self.signer()
455 .contains_key(owner)
456 .await
457 .map_err(Error::signer_failure)
458 }
459
460 #[instrument(level = "trace", skip(self))]
462 pub fn options_mut(&mut self) -> &mut Options {
463 &mut self.options
464 }
465
466 #[instrument(level = "trace", skip(self))]
468 pub fn options(&self) -> &Options {
469 &self.options
470 }
471
472 #[instrument(level = "trace", skip(self))]
474 pub fn chain_id(&self) -> ChainId {
475 self.chain_id
476 }
477
478 pub fn timing_sender(&self) -> Option<mpsc::UnboundedSender<(u64, TimingType)>> {
480 self.timing_sender.clone()
481 }
482
483 #[instrument(level = "trace", skip(self))]
485 pub fn admin_chain_id(&self) -> ChainId {
486 self.client.admin_chain_id
487 }
488
489 #[instrument(level = "trace", skip(self))]
491 pub fn preferred_owner(&self) -> Option<AccountOwner> {
492 self.preferred_owner
493 }
494
495 #[instrument(level = "trace", skip(self))]
497 pub fn set_preferred_owner(&mut self, preferred_owner: AccountOwner) {
498 self.preferred_owner = Some(preferred_owner);
499 }
500
501 #[instrument(level = "trace", skip(self))]
503 pub fn unset_preferred_owner(&mut self) {
504 self.preferred_owner = None;
505 }
506
507 #[instrument(level = "trace")]
509 pub async fn chain_state_view(
510 &self,
511 ) -> Result<crate::worker::ChainStateViewReadGuard<Env::Storage>, LocalNodeError> {
512 self.client.local_node.chain_state_view(self.chain_id).await
513 }
514
515 #[instrument(level = "trace", skip(self))]
518 pub async fn event_stream_publishers(
519 &self,
520 ) -> Result<BTreeMap<ChainId, BTreeSet<StreamId>>, LocalNodeError> {
521 let subscriptions = self
522 .client
523 .local_node
524 .get_event_subscriptions(self.chain_id)
525 .await?;
526 let mut publishers = BTreeMap::<ChainId, BTreeSet<StreamId>>::new();
527 for ((chain_id, stream_id), _) in subscriptions {
530 if self
531 .options
532 .message_policy
533 .accepts_event_stream(&chain_id, &stream_id)
534 {
535 publishers.entry(chain_id).or_default().insert(stream_id);
536 }
537 }
538 if self.chain_id != self.client.admin_chain_id {
539 publishers.entry(self.client.admin_chain_id).or_default();
541 }
542 Ok(publishers)
543 }
544
545 #[instrument(level = "trace")]
547 pub fn subscribe(&self) -> Result<NotificationStream, LocalNodeError> {
548 self.subscribe_to(self.chain_id)
549 }
550
551 #[instrument(level = "trace")]
553 pub fn subscribe_to(&self, chain_id: ChainId) -> Result<NotificationStream, LocalNodeError> {
554 Ok(Box::pin(UnboundedReceiverStream::new(
555 self.client.notifier.subscribe(vec![chain_id]),
556 )))
557 }
558
559 #[instrument(level = "trace")]
561 pub fn storage_client(&self) -> &Env::Storage {
562 self.client.storage_client()
563 }
564
565 #[instrument(level = "trace")]
567 pub async fn chain_info(&self) -> Result<Box<ChainInfo>, LocalNodeError> {
568 let query = ChainInfoQuery::new(self.chain_id);
569 let response = self
570 .client
571 .local_node
572 .handle_chain_info_query(query)
573 .await?;
574 Ok(response.info)
575 }
576
577 #[instrument(level = "trace")]
579 pub async fn chain_info_with_manager_values(&self) -> Result<Box<ChainInfo>, LocalNodeError> {
580 let query = ChainInfoQuery::new(self.chain_id).with_manager_values();
581 let response = self
582 .client
583 .local_node
584 .handle_chain_info_query(query)
585 .await?;
586 Ok(response.info)
587 }
588
589 async fn consensus_state_snapshot(&self) -> Result<ConsensusStateSnapshot, Error> {
598 let info = self.chain_info_with_manager_values().await?;
599 let lock_round = info
600 .manager
601 .requested_locking
602 .as_deref()
603 .map(LockingBlock::round);
604 let timeout_round = info.manager.timeout.as_deref().map(|cert| cert.round);
605 Ok(ConsensusStateSnapshot {
606 next_block_height: info.next_block_height,
607 current_round: info.manager.current_round,
608 lock_round,
609 timeout_round,
610 })
611 }
612
613 pub async fn get_chain_description(&self) -> Result<ChainDescription, Error> {
615 self.client.get_chain_description(self.chain_id).await
616 }
617
618 pub async fn get_application_description(
622 &self,
623 application_id: ApplicationId,
624 ) -> Result<ApplicationDescription, Error> {
625 self.client
626 .get_application_description(application_id)
627 .await
628 }
629
630 #[instrument(level = "trace")]
633 async fn pending_message_bundles(&self) -> Result<Vec<IncomingBundle>, Error> {
634 if self.options.message_policy.is_ignore() {
635 return Ok(Vec::new());
637 }
638
639 let query = ChainInfoQuery::new(self.chain_id).with_pending_message_bundles();
640 let info = self
641 .client
642 .local_node
643 .handle_chain_info_query(query)
644 .await?
645 .info;
646 if self.preferred_owner.is_some_and(|owner| {
647 info.manager
648 .ownership
649 .is_super_owner_no_regular_owners(&owner)
650 }) {
651 ensure!(
653 info.next_block_height >= self.initial_next_block_height,
654 Error::WalletSynchronizationError
655 );
656 }
657
658 let skipped = self.skipped_origins.pin();
659 let mut bundles = info
660 .requested_pending_message_bundles
661 .into_iter()
662 .filter_map(|bundle| bundle.apply_policy(&self.options.message_policy))
663 .filter(|bundle| !skipped.contains(&bundle.origin))
664 .collect::<Vec<_>>();
665 let priority_origins = &self.options.priority_bundle_origins;
666 bundles.sort_by(|a, b| {
667 let a_priority = priority_origins.contains(&a.origin);
668 let b_priority = priority_origins.contains(&b.origin);
669 b_priority
670 .cmp(&a_priority)
671 .then(a.bundle.timestamp.cmp(&b.bundle.timestamp))
672 });
673 bundles.truncate(self.options.max_pending_message_bundles);
674 Ok(bundles)
675 }
676
677 #[instrument(level = "trace")]
681 async fn collect_stream_updates(&self) -> Result<Option<Operation>, Error> {
682 let subscription_map = self
684 .client
685 .local_node
686 .get_event_subscriptions(self.chain_id)
687 .await?;
688 let futures = subscription_map
690 .into_iter()
691 .filter(|((chain_id, stream_id), _)| {
692 self.options
693 .message_policy
694 .accepts_event_stream(chain_id, stream_id)
695 })
696 .map(|((chain_id, stream_id), subscriptions)| {
697 let client = self.client.clone();
698 let previous_index = subscriptions.next_index;
699 async move {
700 let next_index = client
701 .local_node
702 .get_stream_event_count(chain_id, stream_id.clone())
703 .await?;
704 if let Some(next_index) =
705 next_index.filter(|next_index| *next_index > previous_index)
706 {
707 Ok(Some((chain_id, stream_id, previous_index, next_index)))
708 } else {
709 Ok::<_, Error>(None)
710 }
711 }
712 });
713 let all_updates = futures::stream::iter(futures)
714 .buffer_unordered(self.options.max_joined_tasks)
715 .try_collect::<Vec<_>>()
716 .await?
717 .into_iter()
718 .flatten()
719 .collect::<Vec<_>>();
720 let max_events = self.options.max_new_events_per_block;
722 let mut total_events: usize = 0;
723 let mut updates = Vec::new();
724 for (chain_id, stream_id, previous_index, next_index) in all_updates {
725 let new_events = (next_index - previous_index) as usize;
726 if total_events + new_events <= max_events {
727 total_events += new_events;
728 updates.push((chain_id, stream_id, next_index));
729 } else {
730 let remaining = max_events.saturating_sub(total_events);
731 if remaining > 0 {
732 updates.push((chain_id, stream_id, previous_index + remaining as u32));
733 }
734 break;
735 }
736 }
737 if updates.is_empty() {
738 return Ok(None);
739 }
740 Ok(Some(SystemOperation::UpdateStreams(updates).into()))
741 }
742
743 #[instrument(level = "trace")]
744 async fn chain_info_with_committees(&self) -> Result<Box<ChainInfo>, LocalNodeError> {
745 self.client.chain_info_with_committees(self.chain_id).await
746 }
747
748 #[instrument(level = "trace")]
750 async fn epoch_and_committees(
751 &self,
752 ) -> Result<(Epoch, BTreeMap<Epoch, Committee>), LocalNodeError> {
753 let info = self.chain_info_with_committees().await?;
754 let committees = info
755 .requested_committees
756 .ok_or(LocalNodeError::InvalidChainInfoResponse)?;
757 Ok((info.epoch, committees))
758 }
759
760 #[instrument(level = "trace")]
762 pub async fn local_committee(&self) -> Result<Arc<Committee>, Error> {
763 let info = match self.chain_info().await {
764 Ok(info) => info,
765 Err(LocalNodeError::BlobsNotFound(_)) => {
766 self.synchronize_chain_state(self.chain_id).await?;
767 self.chain_info().await?
768 }
769 Err(err) => return Err(err.into()),
770 };
771 let committee = self
772 .client
773 .storage_client()
774 .get_or_load_committee(info.epoch)
775 .await?
776 .ok_or_else(|| LocalNodeError::InactiveChain(self.chain_id))?;
777 Ok(committee)
778 }
779
780 #[instrument(level = "trace")]
782 pub async fn admin_committee(&self) -> Result<(Epoch, Arc<Committee>), LocalNodeError> {
783 self.client.admin_committee().await
784 }
785
786 #[instrument(level = "trace")]
790 pub async fn identity(&self) -> Result<AccountOwner, Error> {
791 let Some(preferred_owner) = self.preferred_owner else {
792 return Err(Error::NoAccountKeyConfigured(self.chain_id));
793 };
794 let manager = self.chain_info().await?.manager;
795 ensure!(
796 manager.ownership.is_active(),
797 LocalNodeError::InactiveChain(self.chain_id)
798 );
799
800 let is_owner = manager
803 .ownership
804 .can_propose_in_multi_leader_round(&preferred_owner);
805
806 if !is_owner {
807 let accepted_owners = manager
808 .ownership
809 .all_owners()
810 .chain(&manager.leader)
811 .collect::<Vec<_>>();
812 warn!(%self.chain_id, ?accepted_owners, ?preferred_owner,
813 "The preferred owner is not configured as an owner of this chain",
814 );
815 return Err(Error::NotAnOwner(self.chain_id));
816 }
817
818 let has_signer = self
819 .signer()
820 .contains_key(&preferred_owner)
821 .await
822 .map_err(Error::signer_failure)?;
823
824 if !has_signer {
825 warn!(%self.chain_id, ?preferred_owner,
826 "Chain is one of the owners but its Signer instance doesn't contain the key",
827 );
828 return Err(Error::CannotFindKeyForChain(self.chain_id));
829 }
830
831 Ok(preferred_owner)
832 }
833
834 #[instrument(level = "trace")]
842 pub async fn prepare_for_owner(&self, owner: AccountOwner) -> Result<Box<ChainInfo>, Error> {
843 ensure!(
844 self.client.has_key_for(&owner).await?,
845 Error::CannotFindKeyForChain(self.chain_id)
846 );
847 self.client
849 .get_chain_description_blob(self.chain_id)
850 .await?;
851
852 let info = self.chain_info().await?;
854
855 ensure!(
857 info.manager
858 .ownership
859 .can_propose_in_multi_leader_round(&owner),
860 Error::NotAnOwner(self.chain_id)
861 );
862
863 Ok(info)
864 }
865
866 #[instrument(level = "trace")]
869 pub async fn prepare_chain(&self) -> Result<Box<ChainInfo>, Error> {
870 #[cfg(with_metrics)]
871 let _latency = super::metrics::PREPARE_CHAIN_LATENCY.measure_latency();
872
873 let mut info = self.synchronize_to_known_height().await?;
874
875 if self.preferred_owner.is_none_or(|owner| {
876 !info
877 .manager
878 .ownership
879 .is_super_owner_no_regular_owners(&owner)
880 }) {
881 info = self.client.synchronize_chain_state(self.chain_id).await?;
885 }
886
887 if info.epoch > self.client.admin_committees().await?.0 {
888 self.client
889 .synchronize_chain_state(self.client.admin_chain_id)
890 .await?;
891 }
892
893 Ok(info)
894 }
895
896 async fn synchronize_to_known_height(&self) -> Result<Box<ChainInfo>, Error> {
901 let info = self
902 .client
903 .download_certificates(self.chain_id, self.initial_next_block_height)
904 .await?;
905 if info.next_block_height == self.initial_next_block_height {
906 ensure!(
908 self.initial_block_hash == info.block_hash,
909 Error::InternalError("Invalid chain of blocks in local node")
910 );
911 }
912 Ok(info)
913 }
914
915 #[instrument(level = "trace", skip(old_committee, latest_certificate))]
917 pub async fn update_validators(
918 &self,
919 old_committee: Option<&Committee>,
920 latest_certificate: Option<CacheArc<ConfirmedBlockCertificate>>,
921 ) -> Result<(), Error> {
922 let update_validators_start = linera_base::time::Instant::now();
923 if let Some(old_committee) = old_committee {
925 let old_committee_start = linera_base::time::Instant::now();
926 self.communicate_chain_updates(old_committee, latest_certificate.clone())
927 .await?;
928 tracing::debug!(
929 old_committee_ms = old_committee_start.elapsed().as_millis(),
930 "communicated chain updates to old committee"
931 );
932 };
933 if let Ok(new_committee) = self.local_committee().await {
934 if Some(&*new_committee) != old_committee {
935 let new_committee_start = linera_base::time::Instant::now();
938 self.communicate_chain_updates(&new_committee, latest_certificate)
939 .await?;
940 tracing::debug!(
941 new_committee_ms = new_committee_start.elapsed().as_millis(),
942 "communicated chain updates to new committee"
943 );
944 }
945 }
946 self.send_timing(update_validators_start, TimingType::UpdateValidators);
947 Ok(())
948 }
949
950 #[instrument(level = "trace", skip(committee, latest_certificate))]
952 pub async fn communicate_chain_updates(
953 &self,
954 committee: &Committee,
955 latest_certificate: Option<CacheArc<ConfirmedBlockCertificate>>,
956 ) -> Result<(), Error> {
957 let delivery = self.options.cross_chain_message_delivery;
958 let height = self.chain_info().await?.next_block_height;
959 self.client
960 .communicate_chain_updates(
961 committee,
962 self.chain_id,
963 height,
964 delivery,
965 latest_certificate,
966 )
967 .await
968 }
969
970 async fn synchronize_publisher_chains(&self) -> Result<(), Error> {
974 let subscriptions = self
975 .client
976 .local_node
977 .get_event_subscriptions(self.chain_id)
978 .await?;
979 let mut streams_by_chain = BTreeMap::<ChainId, BTreeSet<StreamId>>::new();
981 for ((chain_id, stream_id), _) in &subscriptions {
982 if *chain_id != self.chain_id
985 && self
986 .options
987 .message_policy
988 .accepts_event_stream(chain_id, stream_id)
989 {
990 streams_by_chain
991 .entry(*chain_id)
992 .or_default()
993 .insert(stream_id.clone());
994 }
995 }
996 let admin_chain_id = self.client.admin_chain_id;
998 if admin_chain_id != self.chain_id {
999 self.client.synchronize_chain_state(admin_chain_id).await?;
1000 }
1001 let (_, committee) = self.admin_committee().await?;
1003 let nodes = self.client.make_nodes(&committee)?;
1004 let tasks = streams_by_chain
1005 .into_iter()
1006 .filter(|(chain_id, _)| *chain_id != admin_chain_id)
1007 .map(|(chain_id, stream_ids)| {
1008 self.sync_publisher_chain_events(chain_id, stream_ids, &nodes, &committee)
1009 })
1010 .collect::<Vec<_>>();
1011 stream::iter(tasks)
1012 .buffer_unordered(self.options.max_joined_tasks)
1013 .collect::<Vec<_>>()
1014 .await
1015 .into_iter()
1016 .collect::<Result<Vec<_>, _>>()?;
1017 Ok(())
1018 }
1019
1020 async fn sync_publisher_chain_events(
1027 &self,
1028 publisher_chain_id: ChainId,
1029 stream_ids: BTreeSet<StreamId>,
1030 nodes: &[RemoteNode<Env::ValidatorNode>],
1031 committee: &Committee,
1032 ) -> Result<(), Error> {
1033 let stream_ids_ref = &stream_ids;
1034 communicate_with_quorum(
1035 nodes,
1036 committee,
1037 |_: &()| (),
1038 |remote_node| async move {
1039 self.client
1040 .sync_events_from_node(publisher_chain_id, stream_ids_ref, &remote_node)
1041 .await
1042 },
1043 self.options.quorum_grace_period,
1044 )
1045 .await?;
1046 Ok(())
1047 }
1048
1049 #[instrument(level = "debug", skip(self), fields(chain_id = %self.chain_id))]
1058 pub async fn find_received_certificates(&self) -> Result<(), Error> {
1059 debug!("starting find_received_certificates");
1060 #[cfg(with_metrics)]
1061 let _latency = super::metrics::FIND_RECEIVED_CERTIFICATES_LATENCY.measure_latency();
1062 let chain_id = self.chain_id;
1064 let (_, committee) = self.admin_committee().await?;
1065 let nodes = self.client.make_nodes(&committee)?;
1066
1067 let trackers = self
1068 .client
1069 .local_node
1070 .get_received_certificate_trackers(chain_id)
1071 .await?;
1072
1073 trace!("find_received_certificates: read trackers");
1074
1075 let received_log_batches = Arc::new(std::sync::Mutex::new(Vec::new()));
1076 let result = communicate_with_quorum(
1078 &nodes,
1079 &committee,
1080 |_| (),
1081 |remote_node| {
1082 let client = &self.client;
1083 let tracker = trackers.get(&remote_node.public_key).copied().unwrap_or(0);
1084 let received_log_batches = Arc::clone(&received_log_batches);
1085 Box::pin(async move {
1086 let batch = client
1087 .get_received_log_from_validator(chain_id, &remote_node, tracker)
1088 .await?;
1089 let mut batches = received_log_batches.lock().unwrap();
1090 batches.push((remote_node.public_key, batch));
1091 Ok(())
1092 })
1093 },
1094 self.options.quorum_grace_period,
1095 )
1096 .await;
1097
1098 if let Err(error) = result {
1099 error!(
1100 %error,
1101 "Failed to synchronize received_logs from at least a quorum of validators",
1102 );
1103 }
1104
1105 let received_logs: Vec<_> = {
1106 let mut received_log_batches = received_log_batches.lock().unwrap();
1107 std::mem::take(received_log_batches.as_mut())
1108 };
1109
1110 debug!(
1111 received_logs_len = %received_logs.len(),
1112 received_logs_total = %received_logs.iter().map(|x| x.1.len()).sum::<usize>(),
1113 "collected received logs"
1114 );
1115
1116 let (received_logs, mut validator_trackers) = {
1117 (
1118 ReceivedLogs::from_received_result(received_logs.clone()),
1119 ValidatorTrackers::new(received_logs, &trackers),
1120 )
1121 };
1122
1123 debug!(
1124 num_chains = %received_logs.num_chains(),
1125 num_certs = %received_logs.num_certs(),
1126 "find_received_certificates: total number of chains and certificates to sync",
1127 );
1128
1129 let max_blocks_per_chain =
1130 self.options.sender_certificate_download_batch_size / self.options.max_joined_tasks * 2;
1131 for received_log in received_logs.into_batches(
1132 self.options.sender_certificate_download_batch_size,
1133 max_blocks_per_chain,
1134 ) {
1135 validator_trackers = self
1136 .receive_sender_certificates(received_log, validator_trackers, &nodes)
1137 .await?;
1138
1139 self.update_received_certificate_trackers(&validator_trackers)
1140 .await;
1141 }
1142
1143 trace!("find_received_certificates finished");
1144
1145 Ok(())
1146 }
1147
1148 async fn update_received_certificate_trackers(&self, trackers: &ValidatorTrackers) {
1149 let updated_trackers = trackers.to_map();
1150 trace!(?updated_trackers, "updated tracker values");
1151
1152 if let Err(error) = self
1154 .client
1155 .local_node
1156 .update_received_certificate_trackers(self.chain_id, updated_trackers)
1157 .await
1158 {
1159 error!(
1160 chain_id = %self.chain_id,
1161 %error,
1162 "Failed to update the certificate trackers",
1163 );
1164 }
1165 }
1166
1167 async fn receive_sender_certificates(
1170 &self,
1171 mut received_logs: ReceivedLogs,
1172 mut validator_trackers: ValidatorTrackers,
1173 nodes: &[RemoteNode<Env::ValidatorNode>],
1174 ) -> Result<ValidatorTrackers, Error> {
1175 debug!(
1176 num_chains = %received_logs.num_chains(),
1177 num_certs = %received_logs.num_certs(),
1178 "receive_sender_certificates: number of chains and certificates to sync",
1179 );
1180
1181 let local_next_heights = self
1183 .client
1184 .local_node
1185 .next_outbox_heights(received_logs.chains(), self.chain_id)
1186 .await?;
1187
1188 validator_trackers.filter_out_already_known(&mut received_logs, &local_next_heights);
1189
1190 debug!(
1191 remaining_total_certificates = %received_logs.num_certs(),
1192 "receive_sender_certificates: computed remote_heights"
1193 );
1194
1195 let mut other_sender_chains = Vec::new();
1196 let (sender, mut receiver) = mpsc::unbounded_channel::<ChainAndHeight>();
1197
1198 let cert_futures = received_logs.heights_per_chain().into_iter().filter_map({
1199 let received_logs = &received_logs;
1200 let other_sender_chains = &mut other_sender_chains;
1201
1202 move |(sender_chain_id, remote_heights)| {
1203 if remote_heights.is_empty() {
1204 other_sender_chains.push(sender_chain_id);
1208 return None;
1209 };
1210 let remote_heights = remote_heights.into_iter().collect::<Vec<_>>();
1211 let sender = sender.clone();
1212 let client = self.client.clone();
1213 let nodes = nodes.to_vec();
1214 Some(async move {
1215 client
1216 .download_and_process_sender_chain(
1217 sender_chain_id,
1218 &nodes,
1219 received_logs,
1220 remote_heights,
1221 sender,
1222 )
1223 .await
1224 })
1225 }
1226 });
1227
1228 future::join(
1229 stream::iter(cert_futures)
1230 .buffer_unordered(self.options.max_joined_tasks)
1231 .collect::<()>(),
1232 async {
1233 while let Some(chain_and_height) = receiver.recv().await {
1234 validator_trackers.downloaded_cert(chain_and_height);
1235 }
1236 },
1237 )
1238 .await;
1239
1240 debug!(
1241 num_other_chains = %other_sender_chains.len(),
1242 "receive_sender_certificates: processing certificates finished"
1243 );
1244
1245 self.retry_pending_cross_chain_requests_from_sender_chains(nodes, other_sender_chains)
1249 .await;
1250
1251 debug!("receive_sender_certificates: finished processing other_sender_chains");
1252
1253 Ok(validator_trackers)
1254 }
1255
1256 async fn retry_pending_cross_chain_requests_from_sender_chains(
1260 &self,
1261 nodes: &[RemoteNode<Env::ValidatorNode>],
1262 other_sender_chains: Vec<ChainId>,
1263 ) {
1264 let stream = other_sender_chains
1265 .into_iter()
1266 .map(|chain_id| async move {
1267 if let Err(error) = match self
1268 .client
1269 .retry_pending_cross_chain_requests(chain_id)
1270 .await
1271 {
1272 Ok(()) => Ok(()),
1273 Err(LocalNodeError::BlobsNotFound(blob_ids)) => {
1274 if let Err(error) = self
1275 .client
1276 .update_local_node_with_blobs_from(blob_ids.clone(), nodes)
1277 .await
1278 {
1279 error!(
1280 ?blob_ids,
1281 %error,
1282 "Error while attempting to download blobs during retrying outgoing \
1283 messages"
1284 );
1285 }
1286 self.client
1287 .retry_pending_cross_chain_requests(chain_id)
1288 .await
1289 }
1290 err => err,
1291 } {
1292 error!(
1293 %chain_id,
1294 %error,
1295 "Failed to retry outgoing messages from chain"
1296 );
1297 }
1298 })
1299 .collect::<FuturesUnordered<_>>();
1300 stream.for_each(future::ready).await;
1301 }
1302
1303 #[instrument(level = "trace")]
1305 pub async fn transfer(
1306 &self,
1307 owner: AccountOwner,
1308 amount: Amount,
1309 recipient: Account,
1310 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1311 Box::pin(self.execute_operation(SystemOperation::Transfer {
1313 owner,
1314 recipient,
1315 amount,
1316 }))
1317 .await
1318 }
1319
1320 #[instrument(level = "trace")]
1323 pub async fn read_data_blob(
1324 &self,
1325 hash: CryptoHash,
1326 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1327 let blob_id = BlobId {
1328 hash,
1329 blob_type: BlobType::Data,
1330 };
1331 Box::pin(self.execute_operation(SystemOperation::VerifyBlob { blob_id })).await
1332 }
1333
1334 #[instrument(level = "trace")]
1336 pub async fn claim(
1337 &self,
1338 owner: AccountOwner,
1339 target_id: ChainId,
1340 recipient: Account,
1341 amount: Amount,
1342 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1343 Box::pin(self.execute_operation(SystemOperation::Claim {
1344 owner,
1345 target_id,
1346 recipient,
1347 amount,
1348 }))
1349 .await
1350 }
1351
1352 #[instrument(level = "trace")]
1355 pub async fn request_leader_timeout(&self) -> Result<TimeoutCertificate, Error> {
1356 let chain_id = self.chain_id;
1357 let committee = self.local_committee().await?;
1358 let info = self.chain_info().await?;
1359 let committee = &committee;
1360 let height = info.next_block_height;
1361 let round = info.manager.current_round;
1362 let action = CommunicateAction::RequestTimeout {
1363 height,
1364 round,
1365 chain_id,
1366 };
1367 let value = Timeout::new(chain_id, height, info.epoch);
1368 let certificate = Box::new(
1369 self.client
1370 .communicate_chain_action(committee, action, value)
1371 .await?,
1372 );
1373 self.client.handle_certificate(*certificate.clone()).await?;
1374 self.client
1376 .communicate_chain_updates(
1377 committee,
1378 chain_id,
1379 height,
1380 CrossChainMessageDelivery::NonBlocking,
1381 None,
1382 )
1383 .await?;
1384 Ok(*certificate)
1385 }
1386
1387 #[instrument(level = "trace", skip_all)]
1389 pub async fn synchronize_chain_state(
1390 &self,
1391 chain_id: ChainId,
1392 ) -> Result<Box<ChainInfo>, Error> {
1393 self.client.synchronize_chain_state(chain_id).await
1394 }
1395
1396 #[instrument(level = "trace", skip_all)]
1399 pub async fn synchronize_chain_state_from_committee(
1400 &self,
1401 committee: Arc<Committee>,
1402 ) -> Result<Box<ChainInfo>, Error> {
1403 Box::pin(
1404 self.client
1405 .synchronize_chain_state_from_committee(self.chain_id, committee),
1406 )
1407 .await
1408 }
1409
1410 #[instrument(level = "trace", skip(operations, blobs))]
1412 pub async fn execute_operations(
1413 &self,
1414 operations: Vec<Operation>,
1415 blobs: Vec<Blob>,
1416 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1417 let timing_start = linera_base::time::Instant::now();
1418 tracing::debug!("execute_operations started");
1419
1420 let result = loop {
1421 let execute_block_start = linera_base::time::Instant::now();
1422 tracing::debug!("calling execute_block");
1424 match Box::pin(self.execute_block(operations.clone(), blobs.clone())).await {
1425 Ok(ClientOutcome::Committed(certificate)) => {
1426 tracing::debug!(
1427 execute_block_ms = execute_block_start.elapsed().as_millis(),
1428 "execute_block succeeded"
1429 );
1430 self.send_timing(execute_block_start, TimingType::ExecuteBlock);
1431 break Ok(ClientOutcome::Committed(certificate));
1432 }
1433 Ok(ClientOutcome::WaitForTimeout(timeout)) => {
1434 break Ok(ClientOutcome::WaitForTimeout(timeout));
1435 }
1436 Ok(ClientOutcome::Conflict(certificate)) => {
1437 info!(
1438 height = %certificate.block().header.height,
1439 "Another block was committed."
1440 );
1441 break Ok(ClientOutcome::Conflict(certificate));
1442 }
1443 Err(Error::CommunicationError(CommunicationError::Trusted(
1444 NodeError::UnexpectedBlockHeight {
1445 expected_block_height,
1446 found_block_height,
1447 },
1448 ))) if expected_block_height > found_block_height => {
1449 tracing::info!(
1450 chain_id = %self.chain_id,
1451 "Local state is outdated; synchronizing chain"
1452 );
1453 self.synchronize_chain_state(self.chain_id).await?;
1454 }
1455 Err(err) => return Err(err),
1456 };
1457 };
1458
1459 self.send_timing(timing_start, TimingType::ExecuteOperations);
1460 tracing::debug!(
1461 total_execute_operations_ms = timing_start.elapsed().as_millis(),
1462 "execute_operations returning"
1463 );
1464
1465 result
1466 }
1467
1468 pub async fn execute_operation(
1470 &self,
1471 operation: impl Into<Operation>,
1472 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1473 self.execute_operations(vec![operation.into()], vec![])
1474 .await
1475 }
1476
1477 #[instrument(level = "trace", skip(operations, blobs))]
1481 async fn execute_block(
1482 &self,
1483 operations: Vec<Operation>,
1484 blobs: Vec<Blob>,
1485 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1486 #[cfg(with_metrics)]
1487 let _latency = super::metrics::EXECUTE_BLOCK_LATENCY.measure_latency();
1488
1489 let result = self.try_execute_block(operations, blobs).await;
1490 if let Err(error) = &result {
1491 let error_type = error.error_type();
1492 #[cfg(with_metrics)]
1493 super::metrics::BLOCK_STAGING_FAILURES_TOTAL
1494 .with_label_values(&[error_type.as_str()])
1495 .inc();
1496 info!(chain_id = %self.chain_id, %error_type, "Block staging failed");
1497 }
1498 result
1499 }
1500
1501 async fn try_execute_block(
1502 &self,
1503 operations: Vec<Operation>,
1504 blobs: Vec<Blob>,
1505 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1506 let mutex = self.proposal_mutex();
1507 let lock_start = linera_base::time::Instant::now();
1508 let mut proposal_guard = mutex.lock_owned().await;
1509 tracing::debug!(
1510 chain_id = %self.chain_id,
1511 lock_wait_ms = lock_start.elapsed().as_millis(),
1512 "acquired proposal_mutex in execute_block"
1513 );
1514 match self
1520 .process_pending_block_without_prepare(&mut proposal_guard)
1521 .await?
1522 {
1523 ClientOutcome::Committed(Some(certificate)) => {
1524 return Ok(ClientOutcome::Conflict(Box::new(certificate)))
1525 }
1526 ClientOutcome::WaitForTimeout(timeout) => {
1527 return Ok(ClientOutcome::WaitForTimeout(timeout))
1528 }
1529 ClientOutcome::Conflict(certificate) => {
1530 return Ok(ClientOutcome::Conflict(certificate))
1531 }
1532 ClientOutcome::Committed(None) => {}
1533 }
1534
1535 loop {
1536 let transactions = self
1541 .prepend_epochs_messages_and_events(operations.clone())
1542 .await?;
1543
1544 if transactions.is_empty() {
1545 return Err(Error::LocalNodeError(LocalNodeError::WorkerError(
1546 WorkerError::ChainError(Box::new(ChainError::EmptyBlock)),
1547 )));
1548 }
1549
1550 let block = self
1551 .new_pending_block(transactions, blobs.clone(), &mut proposal_guard)
1552 .await?;
1553
1554 match self
1555 .process_pending_block_without_prepare(&mut proposal_guard)
1556 .await?
1557 {
1558 ClientOutcome::Committed(Some(certificate)) if certificate.block() == &block => {
1559 return Ok(ClientOutcome::Committed(certificate));
1560 }
1561 ClientOutcome::Committed(Some(certificate)) => {
1562 return Ok(ClientOutcome::Conflict(Box::new(certificate)));
1563 }
1564 ClientOutcome::Committed(None) => {
1578 tracing::debug!(
1579 chain_id = %self.chain_id,
1580 "pending proposal cleared without committing ours; re-staging and retrying"
1581 );
1582 continue;
1583 }
1584 ClientOutcome::WaitForTimeout(timeout) => {
1585 return Ok(ClientOutcome::WaitForTimeout(timeout));
1586 }
1587 ClientOutcome::Conflict(certificate) => {
1588 return Ok(ClientOutcome::Conflict(certificate));
1589 }
1590 }
1591 }
1592 }
1593
1594 #[instrument(level = "trace", skip(operations))]
1600 async fn prepend_epochs_messages_and_events(
1601 &self,
1602 operations: Vec<Operation>,
1603 ) -> Result<Vec<Transaction>, Error> {
1604 let incoming_bundles = self.pending_message_bundles().await?;
1605 let stream_updates = self.collect_stream_updates().await?;
1606 Ok(self
1607 .collect_epoch_changes()
1608 .await?
1609 .into_iter()
1610 .map(Transaction::ExecuteOperation)
1611 .chain(
1612 incoming_bundles
1613 .into_iter()
1614 .map(Transaction::ReceiveMessages),
1615 )
1616 .chain(
1617 stream_updates
1618 .into_iter()
1619 .map(Transaction::ExecuteOperation),
1620 )
1621 .chain(operations.into_iter().map(Transaction::ExecuteOperation))
1622 .collect::<Vec<_>>())
1623 }
1624
1625 #[instrument(level = "trace", skip(transactions, blobs, proposal_guard))]
1630 async fn new_pending_block(
1631 &self,
1632 transactions: Vec<Transaction>,
1633 blobs: Vec<Blob>,
1634 proposal_guard: &mut Option<PendingProposal>,
1635 ) -> Result<Block, Error> {
1636 let identity = self.identity().await?;
1637
1638 ensure!(
1639 proposal_guard.is_none(),
1640 Error::BlockProposalError(
1641 "Client state already has a pending block; \
1642 use the `linera retry-pending-block` command to commit that first"
1643 )
1644 );
1645 let info = self.chain_info_with_manager_values().await?;
1646 let timestamp = self.next_timestamp(&transactions, info.timestamp);
1647 let proposed_block = ProposedBlock {
1648 epoch: info.epoch,
1649 chain_id: self.chain_id,
1650 transactions,
1651 previous_block_hash: info.block_hash,
1652 height: info.next_block_height,
1653 authenticated_signer: Some(identity),
1654 timestamp,
1655 };
1656
1657 let round = self.round_for_oracle(&info, &identity).await?;
1658 let (block, _, never_reject_origins) = Box::pin(self.client.stage_block_execution(
1661 proposed_block,
1662 round,
1663 blobs.clone(),
1664 self.options.bundle_execution_policy(),
1665 ))
1666 .await?;
1667 if !never_reject_origins.is_empty() {
1670 let skipped = self.skipped_origins.pin();
1671 for origin in never_reject_origins {
1672 skipped.insert(origin);
1673 }
1674 }
1675 let (proposed_block, _) = block.clone().into_proposal();
1676 *proposal_guard = Some(PendingProposal {
1677 block: proposed_block,
1678 blobs,
1679 round: None,
1680 });
1681 Ok(block)
1682 }
1683
1684 #[instrument(level = "trace", skip(transactions))]
1689 fn next_timestamp(&self, transactions: &[Transaction], block_time: Timestamp) -> Timestamp {
1690 let local_time = self.storage_client().clock().current_time();
1691 transactions
1692 .iter()
1693 .filter_map(Transaction::incoming_bundle)
1694 .map(|msg| msg.bundle.timestamp)
1695 .max()
1696 .map_or(local_time, |timestamp| timestamp.max(local_time))
1697 .max(block_time)
1698 }
1699
1700 #[instrument(level = "trace", skip(query))]
1702 pub async fn query_application(
1703 &self,
1704 query: Query,
1705 block_hash: Option<CryptoHash>,
1706 ) -> Result<(QueryOutcome, BlockHeight), Error> {
1707 let mut downloaded_blobs = HashSet::<BlobId>::new();
1708 let mut events = super::EventSetDownloader::new(&self.client);
1709 loop {
1710 let result = self
1711 .client
1712 .local_node
1713 .query_application(self.chain_id, query.clone(), block_hash)
1714 .await;
1715 if let Err(LocalNodeError::BlobsNotFound(blob_ids)) = &result {
1716 let new_blobs = super::filter_new(blob_ids, &downloaded_blobs);
1717 if !new_blobs.is_empty() {
1718 let validators = self.client.validator_nodes().await?;
1719 self.client
1720 .update_local_node_with_blobs_from(new_blobs.clone(), &validators)
1721 .await?;
1722 downloaded_blobs.extend(new_blobs);
1723 continue;
1724 }
1725 }
1726 if let Err(LocalNodeError::EventsNotFound(event_ids)) = &result {
1727 if events.download_new(event_ids).await? {
1728 continue;
1729 }
1730 }
1731 return Ok(result?);
1732 }
1733 }
1734
1735 #[cfg(with_testing)]
1737 #[instrument(level = "trace", skip(query))]
1738 pub async fn query_system_application(
1739 &self,
1740 query: linera_execution::SystemQuery,
1741 ) -> Result<QueryOutcome<linera_execution::SystemResponse>, Error> {
1742 let (
1743 QueryOutcome {
1744 response,
1745 operations,
1746 },
1747 _,
1748 ) = self.query_application(Query::System(query), None).await?;
1749 match response {
1750 linera_execution::QueryResponse::System(response) => Ok(QueryOutcome {
1751 response,
1752 operations,
1753 }),
1754 _ => Err(Error::InternalError("Unexpected response for system query")),
1755 }
1756 }
1757
1758 #[instrument(level = "trace", skip(application_id, query))]
1760 #[cfg(with_testing)]
1761 pub async fn query_user_application<A: Abi>(
1762 &self,
1763 application_id: ApplicationId<A>,
1764 query: &A::Query,
1765 ) -> Result<QueryOutcome<A::QueryResponse>, Error> {
1766 let query = Query::user(application_id, query)?;
1767 let (
1768 QueryOutcome {
1769 response,
1770 operations,
1771 },
1772 _,
1773 ) = self.query_application(query, None).await?;
1774 match response {
1775 linera_execution::QueryResponse::User(response_bytes) => {
1776 let response = serde_json::from_slice(&response_bytes)?;
1777 Ok(QueryOutcome {
1778 response,
1779 operations,
1780 })
1781 }
1782 _ => Err(Error::InternalError("Unexpected response for user query")),
1783 }
1784 }
1785
1786 #[instrument(level = "trace")]
1793 pub async fn query_balance(&self) -> Result<Amount, Error> {
1794 let (balance, _) = Box::pin(self.query_balances_with_owner(AccountOwner::CHAIN)).await?;
1795 Ok(balance)
1796 }
1797
1798 #[instrument(level = "trace", skip(owner))]
1805 pub async fn query_owner_balance(&self, owner: AccountOwner) -> Result<Amount, Error> {
1806 if owner.is_chain() {
1807 Box::pin(self.query_balance()).await
1808 } else {
1809 Ok(Box::pin(self.query_balances_with_owner(owner))
1810 .await?
1811 .1
1812 .unwrap_or(Amount::ZERO))
1813 }
1814 }
1815
1816 #[instrument(level = "trace", skip(owner))]
1823 pub(crate) async fn query_balances_with_owner(
1824 &self,
1825 owner: AccountOwner,
1826 ) -> Result<(Amount, Option<Amount>), Error> {
1827 let incoming_bundles = self.pending_message_bundles().await?;
1828 if incoming_bundles.is_empty() {
1831 let chain_balance = self.local_balance().await?;
1832 let owner_balance = self.local_owner_balance(owner).await?;
1833 return Ok((chain_balance, Some(owner_balance)));
1834 }
1835 let info = self.chain_info().await?;
1836 let transactions = incoming_bundles
1837 .into_iter()
1838 .map(Transaction::ReceiveMessages)
1839 .collect::<Vec<_>>();
1840 let timestamp = self.next_timestamp(&transactions, info.timestamp);
1841 let block = ProposedBlock {
1842 epoch: info.epoch,
1843 chain_id: self.chain_id,
1844 transactions,
1845 previous_block_hash: info.block_hash,
1846 height: info.next_block_height,
1847 authenticated_signer: if owner == AccountOwner::CHAIN {
1848 None
1849 } else {
1850 Some(owner)
1851 },
1852 timestamp,
1853 };
1854 match Box::pin(self.client.stage_block_execution(
1855 block,
1856 None,
1857 Vec::new(),
1858 self.options.bundle_execution_policy(),
1859 ))
1860 .await
1861 {
1862 Ok((_, response, _)) => Ok((
1863 response.info.chain_balance,
1864 response.info.requested_owner_balance,
1865 )),
1866 Err(Error::LocalNodeError(LocalNodeError::WorkerError(WorkerError::ChainError(
1867 error,
1868 )))) if matches!(
1869 &*error,
1870 ChainError::ExecutionError(
1871 execution_error,
1872 ChainExecutionContext::Block
1873 ) if matches!(
1874 **execution_error,
1875 ExecutionError::FeesExceedFunding { .. }
1876 )
1877 ) =>
1878 {
1879 Ok((Amount::ZERO, Some(Amount::ZERO)))
1881 }
1882 Err(error) => Err(error),
1883 }
1884 }
1885
1886 #[instrument(level = "trace")]
1890 pub async fn local_balance(&self) -> Result<Amount, Error> {
1891 let (balance, _) = self.local_balances_with_owner(AccountOwner::CHAIN).await?;
1892 Ok(balance)
1893 }
1894
1895 #[instrument(level = "trace", skip(owner))]
1899 pub async fn local_owner_balance(&self, owner: AccountOwner) -> Result<Amount, Error> {
1900 if owner.is_chain() {
1901 self.local_balance().await
1902 } else {
1903 Ok(self
1904 .local_balances_with_owner(owner)
1905 .await?
1906 .1
1907 .unwrap_or(Amount::ZERO))
1908 }
1909 }
1910
1911 #[instrument(level = "trace", skip(owner))]
1915 pub(crate) async fn local_balances_with_owner(
1916 &self,
1917 owner: AccountOwner,
1918 ) -> Result<(Amount, Option<Amount>), Error> {
1919 ensure!(
1920 self.chain_info().await?.next_block_height >= self.initial_next_block_height,
1921 Error::WalletSynchronizationError
1922 );
1923 let mut query = ChainInfoQuery::new(self.chain_id);
1924 query.request_owner_balance = owner;
1925 let response = self
1926 .client
1927 .local_node
1928 .handle_chain_info_query(query)
1929 .await?;
1930 Ok((
1931 response.info.chain_balance,
1932 response.info.requested_owner_balance,
1933 ))
1934 }
1935
1936 #[instrument(level = "trace")]
1938 pub async fn transfer_to_account(
1939 &self,
1940 from: AccountOwner,
1941 amount: Amount,
1942 account: Account,
1943 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1944 self.transfer(from, amount, account).await
1945 }
1946
1947 #[cfg(with_testing)]
1949 #[instrument(level = "trace")]
1950 pub async fn burn(
1951 &self,
1952 owner: AccountOwner,
1953 amount: Amount,
1954 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
1955 let recipient = Account::burn_address(self.chain_id);
1956 self.transfer(owner, amount, recipient).await
1957 }
1958
1959 #[instrument(level = "trace")]
1961 pub async fn fetch_chain_info(&self) -> Result<Box<ChainInfo>, Error> {
1962 let validators = self.client.validator_nodes().await?;
1963 self.client
1964 .fetch_chain_info(self.chain_id, &validators)
1965 .await
1966 }
1967
1968 #[instrument(level = "trace")]
1983 pub async fn synchronize_up_to(
1984 &self,
1985 next_height: Option<BlockHeight>,
1986 until_block_time: Option<Timestamp>,
1987 ) -> Result<Box<ChainInfo>, Error> {
1988 let (_, committee) = self.client.admin_committee().await?;
1989 let validators = self.client.make_nodes(&committee)?;
1990 Box::pin(self.client.fetch_chain_info(self.chain_id, &validators)).await?;
1991 communicate_with_quorum(
1992 &validators,
1993 &committee,
1994 |_: &()| (),
1995 |remote_node| async move {
1996 self.client
1997 .download_certificates_from(
1998 &remote_node,
1999 self.chain_id,
2000 next_height.unwrap_or(BlockHeight::MAX),
2001 until_block_time,
2002 )
2003 .await?;
2004 Ok(())
2005 },
2006 self.client.options.quorum_grace_period,
2007 )
2008 .await?;
2009 self.client
2010 .local_node
2011 .chain_info(self.chain_id)
2012 .await
2013 .map_err(Into::into)
2014 }
2015
2016 pub async fn synchronize_from_validators(&self) -> Result<Box<ChainInfo>, Error> {
2018 if self.preferred_owner.is_none() {
2019 return self.client.synchronize_chain_state(self.chain_id).await;
2020 }
2021 let info = self.prepare_chain().await?;
2022 self.synchronize_publisher_chains().await?;
2023 self.find_received_certificates().await?;
2024 Ok(info)
2025 }
2026
2027 #[instrument(level = "trace")]
2029 pub async fn process_pending_block(
2030 &self,
2031 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2032 self.prepare_chain().await?;
2033 let mutex = self.proposal_mutex();
2034 let mut proposal_guard = mutex.lock_owned().await;
2035 self.process_pending_block_without_prepare(&mut proposal_guard)
2036 .await
2037 }
2038
2039 #[instrument(level = "debug", skip(self, proposal_guard), fields(chain_id = %self.chain_id))]
2054 async fn process_pending_block_without_prepare(
2055 &self,
2056 proposal_guard: &mut Option<PendingProposal>,
2057 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2058 let mut did_fallback_sync = false;
2060 loop {
2061 let mut snapshot = None;
2066 let err = match self
2067 .process_pending_block_inner(proposal_guard, &mut snapshot)
2068 .await
2069 {
2070 Ok(outcome) => return Ok(outcome),
2071 Err(err) => err,
2072 };
2073 let Some(snapshot) = snapshot else {
2074 return Err(err);
2075 };
2076 let Ok(current) = self.consensus_state_snapshot().await else {
2077 return Err(err);
2078 };
2079 if current == snapshot {
2080 if did_fallback_sync {
2094 return Err(err);
2095 }
2096 did_fallback_sync = true;
2097 if let Err(error) = self.synchronize_chain_state(self.chain_id).await {
2098 tracing::error!(%error, "fallback sync failed after rejected proposal");
2099 return Err(err);
2100 }
2101 let Ok(after_sync) = self.consensus_state_snapshot().await else {
2102 return Err(err);
2103 };
2104 if after_sync == snapshot {
2105 return Err(err);
2106 }
2107 tracing::debug!(
2108 chain_id = %self.chain_id,
2109 ?snapshot,
2110 ?after_sync,
2111 %err,
2112 "fallback sync absorbed new consensus state after rejected proposal; retrying"
2113 );
2114 continue;
2115 }
2116 tracing::debug!(
2117 chain_id = %self.chain_id,
2118 ?snapshot,
2119 ?current,
2120 %err,
2121 "local consensus state advanced during process_pending_block; retrying"
2122 );
2123 }
2124 }
2125
2126 async fn process_pending_block_inner(
2127 &self,
2128 proposal_guard: &mut Option<PendingProposal>,
2129 snapshot: &mut Option<ConsensusStateSnapshot>,
2130 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2131 let process_start = linera_base::time::Instant::now();
2132 tracing::debug!("process_pending_block_without_prepare started");
2133 let info = self.request_leader_timeout_if_needed().await?;
2134 *snapshot = Some(self.consensus_state_snapshot().await?);
2139
2140 if let Some(pending) = &*proposal_guard {
2142 if pending.block.height < info.next_block_height {
2143 tracing::debug!(
2144 "Clearing pending proposal: a block was committed at height {}",
2145 pending.block.height
2146 );
2147 *proposal_guard = None;
2148 }
2149 }
2150
2151 if info.manager.has_locking_block_in_current_round()
2153 && !info.manager.current_round.is_fast()
2154 {
2155 return Box::pin(self.finalize_locking_block(info)).await;
2156 }
2157 let identity = self.identity().await?;
2158
2159 let local_node = &self.client.local_node;
2160 let (block, blobs, owner) = if let Some(locking) = &info.manager.requested_locking {
2162 let (block, blobs) = match &**locking {
2163 LockingBlock::Regular(certificate) => {
2164 let blob_ids = certificate.block().required_blob_ids();
2165 let blobs = local_node
2166 .get_locking_blobs(&blob_ids, self.chain_id)
2167 .await?
2168 .ok_or_else(|| Error::InternalError("Missing local locking blobs"))?;
2169 debug!("Retrying locking block from round {}", certificate.round);
2170 (certificate.block().clone(), blobs)
2171 }
2172 LockingBlock::Fast(proposal) => {
2173 let proposed_block = proposal.content.block.clone();
2174 let blob_ids = proposed_block.published_blob_ids();
2175 let blobs = local_node
2176 .get_locking_blobs(&blob_ids, self.chain_id)
2177 .await?
2178 .ok_or_else(|| Error::InternalError("Missing local locking blobs"))?;
2179 let (block, _, _) = self
2180 .client
2181 .stage_block_execution(
2182 proposed_block,
2183 None,
2184 blobs.clone(),
2185 BundleExecutionPolicy::committed(),
2186 )
2187 .await?;
2188 debug!("Retrying locking block from fast round.");
2189 (block, blobs)
2190 }
2191 };
2192 (block, blobs, identity)
2193 } else if let Some(pending) = proposal_guard.as_ref() {
2194 let owner = match pending.block.authenticated_signer {
2201 Some(staged_owner) if staged_owner != identity => {
2202 if !self.has_key_for(&staged_owner).await? {
2203 if pending.round.is_some_and(|round| round.is_fast()) {
2210 return Err(Error::BlockProposalError(
2211 "pending fast block was signed by an owner whose key is no \
2212 longer available; recover the key or wait for the round to \
2213 time out before retrying",
2214 ));
2215 }
2216 warn!(
2217 ?staged_owner, %identity,
2218 "Discarding pending block: no signer key for its authenticated owner",
2219 );
2220 *proposal_guard = None;
2221 return Ok(ClientOutcome::Committed(None));
2222 }
2223 staged_owner
2224 }
2225 _ => identity,
2226 };
2227 let proposed_block = pending.block.clone();
2228 let blobs = pending.blobs.clone();
2229 let round = self.round_for_oracle(&info, &owner).await?;
2230 let (block, _, _) = self
2231 .client
2232 .stage_block_execution(
2233 proposed_block,
2234 round,
2235 blobs.clone(),
2236 BundleExecutionPolicy::committed(),
2237 )
2238 .await?;
2239 debug!("Proposing the local pending block.");
2240 (block, blobs, owner)
2241 } else {
2242 return Ok(ClientOutcome::Committed(None)); };
2244
2245 let has_oracle_responses = block.has_oracle_responses();
2246 let (proposed_block, outcome) = block.into_proposal();
2247 let round = match self
2248 .round_for_new_proposal(&info, &owner, has_oracle_responses)
2249 .await?
2250 {
2251 Either::Left(round) => round,
2252 Either::Right(timeout) => return Ok(ClientOutcome::WaitForTimeout(timeout)),
2253 };
2254 debug!("Proposing block for round {}", round);
2255 if let Some(pending) = proposal_guard.as_mut() {
2256 pending.round.get_or_insert(round);
2257 }
2258
2259 let already_handled_locally = info
2260 .manager
2261 .already_handled_proposal(round, &proposed_block);
2262 let proposal = if let Some(locking) = info.manager.requested_locking {
2264 Box::new(match *locking {
2265 LockingBlock::Regular(cert) => {
2266 BlockProposal::new_retry_regular(owner, round, cert, self.signer())
2267 .await
2268 .map_err(Error::signer_failure)?
2269 }
2270 LockingBlock::Fast(proposal) => {
2271 BlockProposal::new_retry_fast(owner, round, proposal, self.signer())
2272 .await
2273 .map_err(Error::signer_failure)?
2274 }
2275 })
2276 } else {
2277 Box::new(
2278 BlockProposal::new_initial(owner, round, proposed_block.clone(), self.signer())
2279 .await
2280 .map_err(Error::signer_failure)?,
2281 )
2282 };
2283 if !already_handled_locally {
2284 if let Err(err) = local_node.handle_block_proposal(*proposal.clone()).await {
2286 match err {
2287 LocalNodeError::BlobsNotFound(_) => {
2288 local_node
2289 .handle_pending_blobs(self.chain_id, blobs)
2290 .await?;
2291 local_node.handle_block_proposal(*proposal.clone()).await?;
2292 }
2293 err => return Err(err.into()),
2294 }
2295 }
2296 }
2297 *snapshot = Some(self.consensus_state_snapshot().await?);
2302 let committee = self.local_committee().await?;
2303 let block = Block::new(proposed_block, outcome);
2304 let submit_block_proposal_start = linera_base::time::Instant::now();
2306 let certificate = if round.is_fast() {
2307 let hashed_value = ConfirmedBlock::new(block);
2308 Box::pin(
2309 self.client
2310 .submit_block_proposal(&committee, proposal, hashed_value),
2311 )
2312 .await?
2313 } else {
2314 let hashed_value = ValidatedBlock::new(block);
2315 let certificate = Box::pin(self.client.submit_block_proposal(
2316 &committee,
2317 proposal,
2318 hashed_value.clone(),
2319 ))
2320 .await?;
2321 Box::pin(self.client.finalize_block(&committee, certificate)).await?
2322 };
2323 self.send_timing(submit_block_proposal_start, TimingType::SubmitBlockProposal);
2324 debug!(round = %certificate.round, "Sending confirmed block to validators");
2325 let update_start = linera_base::time::Instant::now();
2326 let certificate = self.client.storage_client().cache_certificate(certificate);
2327 Box::pin(self.update_validators(Some(&committee), Some(certificate.clone()))).await?;
2328 tracing::debug!(
2329 update_validators_ms = update_start.elapsed().as_millis(),
2330 total_process_ms = process_start.elapsed().as_millis(),
2331 "process_pending_block_without_prepare completing"
2332 );
2333 *proposal_guard = None;
2335 Ok(ClientOutcome::Committed(Some(CacheArc::unwrap_or_clone(
2336 certificate,
2337 ))))
2338 }
2339
2340 fn send_timing(&self, start: Instant, timing_type: TimingType) {
2341 let Some(sender) = &self.timing_sender else {
2342 return;
2343 };
2344 if let Err(err) = sender.send((start.elapsed().as_millis() as u64, timing_type)) {
2345 tracing::warn!(%err, "Failed to send timing info");
2346 }
2347 }
2348
2349 async fn request_leader_timeout_if_needed(&self) -> Result<Box<ChainInfo>, Error> {
2352 let mut info = self.chain_info_with_manager_values().await?;
2353 if let Some(round_timeout) = info.manager.round_timeout {
2356 if round_timeout <= self.storage_client().clock().current_time() {
2357 if let Err(e) = self.request_leader_timeout().await {
2358 debug!("Failed to obtain a timeout certificate: {}", e);
2359 } else {
2360 info = self.chain_info_with_manager_values().await?;
2361 }
2362 }
2363 }
2364 Ok(info)
2365 }
2366
2367 async fn finalize_locking_block(
2371 &self,
2372 info: Box<ChainInfo>,
2373 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2374 let locking = info
2375 .manager
2376 .requested_locking
2377 .expect("Should have a locking block");
2378 let LockingBlock::Regular(certificate) = *locking else {
2379 panic!("Should have a locking validated block");
2380 };
2381 debug!(
2382 round = %certificate.round,
2383 "Finalizing locking block"
2384 );
2385 let committee = self.local_committee().await?;
2386 let certificate =
2387 Box::pin(self.client.finalize_block(&committee, certificate.clone())).await?;
2388 let certificate = self.client.storage_client().cache_certificate(certificate);
2389 Box::pin(self.update_validators(Some(&committee), Some(certificate.clone()))).await?;
2390 Ok(ClientOutcome::Committed(Some(CacheArc::unwrap_or_clone(
2391 certificate,
2392 ))))
2393 }
2394
2395 async fn round_for_oracle(
2397 &self,
2398 info: &ChainInfo,
2399 identity: &AccountOwner,
2400 ) -> Result<Option<u32>, Error> {
2401 match self.round_for_new_proposal(info, identity, true).await {
2403 Ok(Either::Left(round)) => Ok(round.multi_leader()),
2405 Err(Error::BlockProposalError(_)) | Ok(Either::Right(_)) => Ok(None),
2409 Err(err) => Err(err),
2410 }
2411 }
2412
2413 async fn round_for_new_proposal(
2415 &self,
2416 info: &ChainInfo,
2417 identity: &AccountOwner,
2418 has_oracle_responses: bool,
2419 ) -> Result<Either<Round, RoundTimeout>, Error> {
2420 let manager = &info.manager;
2421 let seed = self
2422 .client
2423 .local_node
2424 .get_manager_seed(self.chain_id)
2425 .await?;
2426 let skip_fast = manager.current_round.is_fast()
2431 && (has_oracle_responses || !self.options.allow_fast_blocks);
2432 let conflict = manager
2433 .requested_signed_proposal
2434 .as_ref()
2435 .into_iter()
2436 .chain(&manager.requested_proposed)
2437 .any(|proposal| proposal.content.round == manager.current_round)
2438 || skip_fast;
2439 let round = if !conflict {
2440 manager.current_round
2441 } else if let Some(round) = manager
2442 .ownership
2443 .next_round(manager.current_round)
2444 .filter(|_| manager.current_round.is_multi_leader() || manager.current_round.is_fast())
2445 {
2446 round
2447 } else if let Some(timeout) = info.round_timeout() {
2448 return Ok(Either::Right(timeout));
2449 } else {
2450 return Err(Error::BlockProposalError(
2451 "Conflicting proposal in the current round",
2452 ));
2453 };
2454 let current_committee = self
2455 .local_committee()
2456 .await?
2457 .validators
2458 .values()
2459 .map(|v| (AccountOwner::from(v.account_public_key), v.votes))
2460 .collect();
2461 if manager.should_propose(identity, round, seed, ¤t_committee) {
2462 return Ok(Either::Left(round));
2463 }
2464 if let Some(timeout) = info.round_timeout() {
2465 return Ok(Either::Right(timeout));
2466 }
2467 Err(Error::BlockProposalError(
2468 "Not a leader in the current round",
2469 ))
2470 }
2471
2472 #[instrument(level = "trace")]
2479 pub async fn clear_pending_proposal(&self) {
2480 *self.proposal_mutex().lock().await = None;
2481 }
2482
2483 #[cfg(with_testing)]
2487 #[instrument(level = "trace")]
2488 pub async fn rotate_key_pair(
2489 &self,
2490 public_key: linera_base::crypto::AccountPublicKey,
2491 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2492 Box::pin(self.transfer_ownership(public_key.into())).await
2493 }
2494
2495 #[instrument(level = "trace")]
2497 pub async fn transfer_ownership(
2498 &self,
2499 new_owner: AccountOwner,
2500 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2501 Box::pin(self.execute_operation(SystemOperation::ChangeOwnership {
2502 super_owners: vec![new_owner],
2503 owners: Vec::new(),
2504 multi_leader_rounds: 5,
2505 open_multi_leader_rounds: false,
2506 timeout_config: TimeoutConfig::default(),
2507 }))
2508 .await
2509 }
2510
2511 #[instrument(level = "trace")]
2513 pub async fn share_ownership(
2514 &self,
2515 new_owner: AccountOwner,
2516 new_weight: u64,
2517 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2518 let ownership = self.prepare_chain().await?.manager.ownership;
2519 ensure!(
2520 ownership.is_active(),
2521 ChainError::InactiveChain(self.chain_id)
2522 );
2523 let mut owners = ownership.owners.into_iter().collect::<Vec<_>>();
2524 owners.extend(ownership.super_owners.into_iter().zip(iter::repeat(100)));
2525 owners.push((new_owner, new_weight));
2526 let operations = vec![Operation::system(SystemOperation::ChangeOwnership {
2527 super_owners: Vec::new(),
2528 owners,
2529 multi_leader_rounds: ownership.multi_leader_rounds,
2530 open_multi_leader_rounds: ownership.open_multi_leader_rounds,
2531 timeout_config: ownership.timeout_config,
2532 })];
2533 match self.execute_block(operations, vec![]).await? {
2534 ClientOutcome::Committed(certificate) => Ok(ClientOutcome::Committed(certificate)),
2535 ClientOutcome::Conflict(certificate) => {
2536 info!(
2537 height = %certificate.block().header.height,
2538 "Another block was committed."
2539 );
2540 Ok(ClientOutcome::Conflict(certificate))
2541 }
2542 ClientOutcome::WaitForTimeout(timeout) => Ok(ClientOutcome::WaitForTimeout(timeout)),
2543 }
2544 }
2545
2546 #[instrument(level = "trace")]
2548 pub async fn query_chain_ownership(&self) -> Result<ChainOwnership, Error> {
2549 Ok(self
2550 .client
2551 .local_node
2552 .chain_state_view(self.chain_id)
2553 .await?
2554 .execution_state
2555 .system
2556 .ownership
2557 .get()
2558 .await?
2559 .clone())
2560 }
2561
2562 #[instrument(level = "trace")]
2565 pub async fn change_ownership(
2566 &self,
2567 ownership: ChainOwnership,
2568 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2569 Box::pin(self.execute_operation(SystemOperation::ChangeOwnership {
2570 super_owners: ownership.super_owners.into_iter().collect(),
2571 owners: ownership.owners.into_iter().collect(),
2572 multi_leader_rounds: ownership.multi_leader_rounds,
2573 open_multi_leader_rounds: ownership.open_multi_leader_rounds,
2574 timeout_config: ownership.timeout_config.clone(),
2575 }))
2576 .await
2577 }
2578
2579 #[instrument(level = "trace")]
2581 pub async fn query_application_permissions(&self) -> Result<ApplicationPermissions, Error> {
2582 Ok(self
2583 .client
2584 .local_node
2585 .chain_state_view(self.chain_id)
2586 .await?
2587 .execution_state
2588 .system
2589 .application_permissions
2590 .get()
2591 .await?
2592 .clone())
2593 }
2594
2595 #[instrument(level = "trace", skip(application_permissions))]
2597 pub async fn change_application_permissions(
2598 &self,
2599 application_permissions: ApplicationPermissions,
2600 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2601 Box::pin(
2602 self.execute_operation(SystemOperation::ChangeApplicationPermissions(
2603 application_permissions,
2604 )),
2605 )
2606 .await
2607 }
2608
2609 #[instrument(level = "trace", skip(self))]
2611 pub async fn open_chain(
2612 &self,
2613 ownership: ChainOwnership,
2614 application_permissions: ApplicationPermissions,
2615 balance: Amount,
2616 ) -> Result<ClientOutcome<(ChainDescription, ConfirmedBlockCertificate)>, Error> {
2617 let config = OpenChainConfig {
2618 ownership: ownership.clone(),
2619 balance,
2620 application_permissions: application_permissions.clone(),
2621 };
2622 let operation = Operation::system(SystemOperation::OpenChain(config));
2623 let certificate = match self.execute_block(vec![operation], vec![]).await? {
2624 ClientOutcome::Committed(certificate) => certificate,
2625 ClientOutcome::Conflict(certificate) => {
2626 return Ok(ClientOutcome::Conflict(certificate));
2627 }
2628 ClientOutcome::WaitForTimeout(timeout) => {
2629 return Ok(ClientOutcome::WaitForTimeout(timeout));
2630 }
2631 };
2632 let chain_blob = certificate
2634 .block()
2635 .body
2636 .blobs
2637 .last()
2638 .and_then(|blobs| blobs.last())
2639 .ok_or_else(|| Error::InternalError("Failed to create a new chain"))?;
2640 let description = bcs::from_bytes::<ChainDescription>(chain_blob.bytes())?;
2641 for owner in ownership.all_owners() {
2643 if self.client.has_key_for(owner).await? {
2644 self.client
2645 .extend_chain_mode(description.id(), ListeningMode::FullChain);
2646 break;
2647 }
2648 }
2649 self.client
2650 .retry_pending_cross_chain_requests(self.chain_id)
2651 .await?;
2652 Ok(ClientOutcome::Committed((description, certificate)))
2653 }
2654
2655 #[instrument(level = "trace")]
2658 pub async fn close_chain(
2659 &self,
2660 ) -> Result<ClientOutcome<Option<ConfirmedBlockCertificate>>, Error> {
2661 match Box::pin(self.execute_operation(SystemOperation::CloseChain)).await {
2662 Ok(outcome) => Ok(outcome.map(Some)),
2663 Err(Error::LocalNodeError(LocalNodeError::WorkerError(WorkerError::ChainError(
2664 chain_error,
2665 )))) if matches!(*chain_error, ChainError::ClosedChain) => {
2666 Ok(ClientOutcome::Committed(None)) }
2668 Err(error) => Err(error),
2669 }
2670 }
2671
2672 #[cfg(not(target_arch = "wasm32"))]
2674 #[instrument(level = "trace", skip(contract, service))]
2675 pub async fn publish_module(
2676 &self,
2677 contract: Bytecode,
2678 service: Bytecode,
2679 vm_runtime: VmRuntime,
2680 ) -> Result<ClientOutcome<(ModuleId, ConfirmedBlockCertificate)>, Error> {
2681 let (blobs, module_id) = create_bytecode_blobs(contract, service, vm_runtime).await;
2682 Box::pin(self.publish_module_blobs(blobs, module_id)).await
2683 }
2684
2685 #[cfg(not(target_arch = "wasm32"))]
2687 #[instrument(level = "trace", skip(blobs, module_id))]
2688 pub async fn publish_module_blobs(
2689 &self,
2690 blobs: Vec<Blob>,
2691 module_id: ModuleId,
2692 ) -> Result<ClientOutcome<(ModuleId, ConfirmedBlockCertificate)>, Error> {
2693 self.execute_operations(
2694 vec![Operation::system(SystemOperation::PublishModule {
2695 module_id,
2696 })],
2697 blobs,
2698 )
2699 .await?
2700 .try_map(|certificate| Ok((module_id, certificate)))
2701 }
2702
2703 #[instrument(level = "trace", skip(bytes))]
2705 pub async fn publish_data_blobs(
2706 &self,
2707 bytes: Vec<Vec<u8>>,
2708 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2709 let blobs = bytes.into_iter().map(Blob::new_data);
2710 let publish_blob_operations = blobs
2711 .clone()
2712 .map(|blob| {
2713 Operation::system(SystemOperation::PublishDataBlob {
2714 blob_hash: blob.id().hash,
2715 })
2716 })
2717 .collect();
2718 self.execute_operations(publish_blob_operations, blobs.collect())
2719 .await
2720 }
2721
2722 #[instrument(level = "trace", skip(bytes))]
2724 pub async fn publish_data_blob(
2725 &self,
2726 bytes: Vec<u8>,
2727 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2728 Box::pin(self.publish_data_blobs(vec![bytes])).await
2729 }
2730
2731 #[instrument(
2733 level = "trace",
2734 skip(self, parameters, instantiation_argument, required_application_ids)
2735 )]
2736 pub async fn create_application<
2737 A: Abi,
2738 Parameters: Serialize,
2739 InstantiationArgument: Serialize,
2740 >(
2741 &self,
2742 module_id: ModuleId<A, Parameters, InstantiationArgument>,
2743 parameters: &Parameters,
2744 instantiation_argument: &InstantiationArgument,
2745 required_application_ids: Vec<ApplicationId>,
2746 ) -> Result<ClientOutcome<(ApplicationId<A>, ConfirmedBlockCertificate)>, Error> {
2747 let instantiation_argument = serde_json::to_vec(instantiation_argument)?;
2748 let parameters = serde_json::to_vec(parameters)?;
2749 Ok(Box::pin(self.create_application_untyped(
2750 module_id.forget_abi(),
2751 parameters,
2752 instantiation_argument,
2753 required_application_ids,
2754 ))
2755 .await?
2756 .map(|(app_id, cert)| (app_id.with_abi(), cert)))
2757 }
2758
2759 #[instrument(
2761 level = "trace",
2762 skip(
2763 self,
2764 module_id,
2765 parameters,
2766 instantiation_argument,
2767 required_application_ids
2768 )
2769 )]
2770 pub async fn create_application_untyped(
2771 &self,
2772 module_id: ModuleId,
2773 parameters: Vec<u8>,
2774 instantiation_argument: Vec<u8>,
2775 required_application_ids: Vec<ApplicationId>,
2776 ) -> Result<ClientOutcome<(ApplicationId, ConfirmedBlockCertificate)>, Error> {
2777 Box::pin(self.execute_operation(SystemOperation::CreateApplication {
2778 module_id,
2779 parameters,
2780 instantiation_argument,
2781 required_application_ids,
2782 }))
2783 .await?
2784 .try_map(|certificate| {
2785 let mut creation = certificate
2787 .block()
2788 .created_blob_ids()
2789 .into_iter()
2790 .filter(|blob_id| blob_id.blob_type == BlobType::ApplicationDescription)
2791 .collect::<Vec<_>>();
2792 if creation.len() > 1 {
2793 return Err(Error::InternalError(
2794 "Unexpected number of application descriptions published",
2795 ));
2796 }
2797 let blob_id = creation.pop().ok_or(Error::InternalError(
2798 "ApplicationDescription blob not found.",
2799 ))?;
2800 let id = ApplicationId::new(blob_id.hash);
2801 Ok((id, certificate))
2802 })
2803 }
2804
2805 #[instrument(level = "trace", skip(committee))]
2807 pub async fn stage_new_committee(
2808 &self,
2809 committee: Committee,
2810 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2811 let blob = Blob::new(BlobContent::new_committee(bcs::to_bytes(&committee)?));
2812 let blob_hash = blob.id().hash;
2813 match self
2814 .execute_operations(
2815 vec![Operation::system(SystemOperation::Admin(
2816 AdminOperation::PublishCommitteeBlob { blob_hash },
2817 ))],
2818 vec![blob],
2819 )
2820 .await?
2821 {
2822 ClientOutcome::Committed(_) => {}
2823 outcome @ ClientOutcome::WaitForTimeout(_) | outcome @ ClientOutcome::Conflict(_) => {
2824 return Ok(outcome)
2825 }
2826 }
2827 let epoch = Box::pin(self.chain_info()).await?.epoch.try_add_one()?;
2828 Box::pin(
2829 self.execute_operation(SystemOperation::Admin(AdminOperation::CreateCommittee {
2830 epoch,
2831 blob_hash,
2832 })),
2833 )
2834 .await
2835 }
2836
2837 #[instrument(level = "trace")]
2843 pub async fn process_inbox(
2844 &self,
2845 ) -> Result<(Vec<ConfirmedBlockCertificate>, Option<RoundTimeout>), Error> {
2846 self.prepare_chain().await?;
2847 self.process_inbox_without_prepare().await
2848 }
2849
2850 #[instrument(level = "trace")]
2856 pub async fn process_inbox_without_prepare(
2857 &self,
2858 ) -> Result<(Vec<ConfirmedBlockCertificate>, Option<RoundTimeout>), Error> {
2859 #[cfg(with_metrics)]
2860 let _latency = super::metrics::PROCESS_INBOX_WITHOUT_PREPARE_LATENCY.measure_latency();
2861
2862 let mut certificates = Vec::new();
2863 loop {
2864 match self.execute_block(vec![], vec![]).await {
2868 Ok(ClientOutcome::Committed(certificate)) => certificates.push(certificate),
2869 Ok(ClientOutcome::Conflict(certificate)) => certificates.push(*certificate),
2870 Ok(ClientOutcome::WaitForTimeout(timeout)) => {
2871 return Ok((certificates, Some(timeout)));
2872 }
2873 Err(Error::LocalNodeError(LocalNodeError::WorkerError(
2875 WorkerError::ChainError(chain_error),
2876 ))) if matches!(*chain_error, ChainError::EmptyBlock) => {
2877 return Ok((certificates, None));
2878 }
2879 Err(error) => return Err(error),
2880 };
2881 }
2882 }
2883
2884 async fn collect_epoch_changes(&self) -> Result<Vec<Operation>, Error> {
2887 let (mut min_epoch, mut next_epoch) = {
2888 let (epoch, committees) = self.epoch_and_committees().await?;
2889 let min_epoch = *committees.keys().next().unwrap_or(&Epoch::ZERO);
2890 (min_epoch, epoch.try_add_one()?)
2891 };
2892 let mut epoch_change_ops = Vec::new();
2893 while self
2894 .has_admin_event(EPOCH_STREAM_NAME, next_epoch.0)
2895 .await?
2896 {
2897 epoch_change_ops.push(Operation::system(SystemOperation::ProcessNewEpoch(
2898 next_epoch,
2899 )));
2900 next_epoch.try_add_assign_one()?;
2901 }
2902 while self
2903 .has_admin_event(REMOVED_EPOCH_STREAM_NAME, min_epoch.0)
2904 .await?
2905 {
2906 epoch_change_ops.push(Operation::system(SystemOperation::ProcessRemovedEpoch(
2907 min_epoch,
2908 )));
2909 min_epoch.try_add_assign_one()?;
2910 }
2911 Ok(epoch_change_ops)
2912 }
2913
2914 async fn has_admin_event(&self, stream_name: &[u8], index: u32) -> Result<bool, Error> {
2917 let event_id = EventId {
2918 chain_id: self.client.admin_chain_id,
2919 stream_id: StreamId::system(stream_name),
2920 index,
2921 };
2922 Ok(self
2923 .client
2924 .storage_client()
2925 .read_event(event_id)
2926 .await?
2927 .is_some())
2928 }
2929
2930 pub async fn events_from_index(
2932 &self,
2933 stream_id: StreamId,
2934 start_index: u32,
2935 ) -> Result<Vec<IndexAndEvent>, Error> {
2936 Ok(self
2937 .client
2938 .storage_client()
2939 .read_events_from_index(&self.chain_id, &stream_id, start_index)
2940 .await?)
2941 }
2942
2943 #[instrument(level = "trace")]
2948 pub async fn revoke_epochs(
2949 &self,
2950 revoked_epoch: Epoch,
2951 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2952 self.prepare_chain().await?;
2953 let (current_epoch, committees) = self.epoch_and_committees().await?;
2954 ensure!(
2955 revoked_epoch < current_epoch,
2956 Error::CannotRevokeCurrentEpoch(current_epoch)
2957 );
2958 ensure!(
2959 committees.contains_key(&revoked_epoch),
2960 Error::EpochAlreadyRevoked
2961 );
2962 let operations = committees
2963 .keys()
2964 .filter_map(|epoch| {
2965 if *epoch <= revoked_epoch {
2966 Some(Operation::system(SystemOperation::Admin(
2967 AdminOperation::RemoveCommittee { epoch: *epoch },
2968 )))
2969 } else {
2970 None
2971 }
2972 })
2973 .collect();
2974 self.execute_operations(operations, vec![]).await
2975 }
2976
2977 #[cfg(with_testing)]
2981 #[instrument(level = "trace")]
2982 pub async fn transfer_to_account_unsafe_unconfirmed(
2983 &self,
2984 owner: AccountOwner,
2985 amount: Amount,
2986 recipient: Account,
2987 ) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
2988 Box::pin(self.execute_operation(SystemOperation::Transfer {
2989 owner,
2990 recipient,
2991 amount,
2992 }))
2993 .await
2994 }
2995
2996 #[instrument(level = "trace", skip(hash))]
2998 pub async fn read_confirmed_block(
2999 &self,
3000 hash: CryptoHash,
3001 ) -> Result<Arc<ConfirmedBlock>, Error> {
3002 self.client
3003 .storage_client()
3004 .read_confirmed_block(hash)
3005 .await?
3006 .ok_or(Error::MissingConfirmedBlock(hash))
3007 .map(|b| b.into_std())
3008 }
3009
3010 #[instrument(level = "trace", skip(hash))]
3012 pub async fn read_certificate(
3013 &self,
3014 hash: CryptoHash,
3015 ) -> Result<CacheArc<ConfirmedBlockCertificate>, Error> {
3016 self.client
3017 .storage_client()
3018 .read_certificate(hash)
3019 .await?
3020 .ok_or(Error::ReadCertificatesError(vec![hash]))
3021 }
3022
3023 #[instrument(level = "trace")]
3025 pub async fn retry_pending_outgoing_messages(&self) -> Result<(), Error> {
3026 self.client
3027 .retry_pending_cross_chain_requests(self.chain_id)
3028 .await?;
3029 Ok(())
3030 }
3031
3032 #[instrument(level = "trace", skip(local_node))]
3033 async fn maybe_local_chain_info(
3034 &self,
3035 chain_id: ChainId,
3036 local_node: &LocalNodeClient<Env::Storage>,
3037 ) -> Result<Option<Box<ChainInfo>>, Error> {
3038 match local_node.chain_info(chain_id).await {
3039 Ok(info) => Ok(Some(info)),
3040 Err(LocalNodeError::BlobsNotFound(_) | LocalNodeError::InactiveChain(_)) => Ok(None),
3041 Err(err) => Err(err.into()),
3042 }
3043 }
3044
3045 #[instrument(level = "trace", skip(chain_id, local_node))]
3046 async fn local_next_block_height(
3047 &self,
3048 chain_id: ChainId,
3049 local_node: &LocalNodeClient<Env::Storage>,
3050 ) -> Result<BlockHeight, Error> {
3051 Ok(self
3052 .maybe_local_chain_info(chain_id, local_node)
3053 .await?
3054 .map_or(BlockHeight::ZERO, |info| info.next_block_height))
3055 }
3056
3057 #[instrument(level = "trace")]
3060 async fn local_next_height_to_receive(&self, origin: ChainId) -> Result<BlockHeight, Error> {
3061 Ok(self
3062 .client
3063 .local_node
3064 .get_inbox_next_height(self.chain_id, origin)
3065 .await?)
3066 }
3067
3068 #[instrument(level = "trace", skip(remote_node, local_node, notification))]
3069 async fn process_notification(
3070 &self,
3071 remote_node: RemoteNode<Env::ValidatorNode>,
3072 local_node: LocalNodeClient<Env::Storage>,
3073 notification: Notification,
3074 ) -> Result<(), Error> {
3075 let listening_mode = self.client.chain_mode(notification.chain_id);
3076 let is_relevant = listening_mode
3077 .as_ref()
3078 .is_some_and(|mode| mode.is_relevant(¬ification.reason));
3079 if !is_relevant {
3080 tracing::trace!(
3081 chain_id = %notification.chain_id,
3082 reason = ?notification.reason,
3083 ?listening_mode,
3084 "Ignoring notification due to listening mode"
3085 );
3086 return Ok(());
3087 }
3088 match notification.reason {
3089 Reason::NewIncomingBundle { origin, height } => {
3090 if self.options.message_policy.ignores_origin(&origin) {
3091 trace!(
3092 chain_id = %self.chain_id,
3093 %origin,
3094 %height,
3095 "Skipping NewIncomingBundle notification: origin filtered by message_policy"
3096 );
3097 return Ok(());
3098 }
3099 if self.local_next_height_to_receive(origin).await? > height {
3100 debug!(
3101 chain_id = %self.chain_id,
3102 "Accepting redundant notification for new message"
3103 );
3104 return Ok(());
3105 }
3106 self.client
3107 .download_sender_block_with_sending_ancestors(
3108 self.chain_id,
3109 origin,
3110 height,
3111 &remote_node,
3112 )
3113 .await?;
3114 if self.local_next_height_to_receive(origin).await? <= height {
3115 info!(
3116 chain_id = %self.chain_id,
3117 "NewIncomingBundle: Fail to synchronize new message after notification"
3118 );
3119 }
3120 }
3121 Reason::NewBlock {
3122 height,
3123 hash,
3124 event_streams,
3125 ..
3126 } => {
3127 let chain_id = notification.chain_id;
3128 let local_height = self.local_next_block_height(chain_id, &local_node).await?;
3129 if local_height > height {
3130 debug!(
3131 chain_id = %self.chain_id,
3132 "Accepting redundant notification for new block"
3133 );
3134 return Ok(());
3135 }
3136 if let Some(ListeningMode::EventsOnly(subscribed)) =
3140 self.client.chain_mode(chain_id)
3141 {
3142 if !event_streams.is_empty() {
3143 self.client
3144 .download_event_bearing_blocks(
3145 chain_id,
3146 BTreeSet::from([(height, hash)]),
3147 local_height,
3148 &subscribed,
3149 &remote_node,
3150 )
3151 .await?;
3152 }
3153 } else {
3154 self.client
3155 .synchronize_chain_state_from(&remote_node, chain_id)
3156 .await?;
3157 if self.local_next_block_height(chain_id, &local_node).await? <= height {
3158 error!("NewBlock: Fail to synchronize new block after notification");
3159 }
3160 }
3161 }
3162 Reason::NewEvents { height, hash, .. } => {
3163 let chain_id = notification.chain_id;
3164 let local_height = self.local_next_block_height(chain_id, &local_node).await?;
3165 if local_height > height {
3166 debug!(
3167 chain_id = %self.chain_id,
3168 "Accepting redundant notification for new events"
3169 );
3170 return Ok(());
3171 }
3172 let subscribed = match self.client.chain_mode(chain_id) {
3173 Some(ListeningMode::EventsOnly(streams)) => streams,
3174 _ => return Ok(()),
3175 };
3176 self.client
3177 .download_event_bearing_blocks(
3178 chain_id,
3179 BTreeSet::from([(height, hash)]),
3180 local_height,
3181 &subscribed,
3182 &remote_node,
3183 )
3184 .await?;
3185 }
3186 Reason::NewRound { height, round } => {
3187 let chain_id = notification.chain_id;
3188 if let Some(info) = self.maybe_local_chain_info(chain_id, &local_node).await? {
3189 if (info.next_block_height, info.manager.current_round) >= (height, round) {
3190 debug!(
3191 chain_id = %self.chain_id,
3192 "Accepting redundant notification for new round"
3193 );
3194 return Ok(());
3195 }
3196 }
3197 self.client
3198 .synchronize_chain_state_from(&remote_node, chain_id)
3199 .await?;
3200 let Some(info) = self.maybe_local_chain_info(chain_id, &local_node).await? else {
3201 error!(
3202 chain_id = %self.chain_id,
3203 "NewRound: Fail to read local chain info for {chain_id}"
3204 );
3205 return Ok(());
3206 };
3207 if (info.next_block_height, info.manager.current_round) < (height, round) {
3208 info!(
3209 chain_id = %self.chain_id,
3210 "NewRound: Fail to synchronize new block after notification"
3211 );
3212 }
3213 }
3214 Reason::BlockExecuted { .. } => {
3215 }
3217 }
3218 Ok(())
3219 }
3220
3221 pub fn is_tracked(&self) -> bool {
3223 self.client.is_tracked(self.chain_id)
3224 }
3225
3226 pub fn listening_mode(&self) -> Option<ListeningMode> {
3228 self.client.chain_mode(self.chain_id)
3229 }
3230
3231 #[instrument(level = "trace", fields(chain_id = ?self.chain_id))]
3236 pub async fn listen(
3237 &self,
3238 ) -> Result<(impl Future<Output = ()>, AbortOnDrop, NotificationStream), Error> {
3239 use future::FutureExt as _;
3240
3241 async fn await_while_polling<F: FusedFuture>(
3242 future: F,
3243 background_work: impl FusedStream<Item = ()>,
3244 ) -> F::Output {
3245 tokio::pin!(future);
3246 tokio::pin!(background_work);
3247 loop {
3248 futures::select! {
3249 _ = background_work.next() => (),
3250 result = future => return result,
3251 }
3252 }
3253 }
3254
3255 let mut senders = HashMap::new();
3256 let mut circuit_breakers: HashMap<ValidatorPublicKey, CircuitBreakerState> = HashMap::new();
3257 let notifications = self.subscribe()?;
3258 let (abortable_notifications, abort) = stream::abortable(self.subscribe()?);
3259
3260 let mut process_notifications = FuturesUnordered::new();
3267
3268 match self
3269 .update_notification_streams(&mut senders, &mut circuit_breakers)
3270 .await
3271 {
3272 Ok(handler) => process_notifications.push(handler),
3273 Err(error) => error!("Failed to update committee: {error}"),
3274 };
3275
3276 let this = self.clone();
3277 let update_streams = async move {
3278 let mut abortable_notifications = abortable_notifications.fuse();
3279
3280 while let Some(notification) =
3281 await_while_polling(abortable_notifications.next(), &mut process_notifications)
3282 .await
3283 {
3284 if let Reason::NewBlock { .. } = notification.reason {
3288 let is_events_only = this
3289 .listening_mode()
3290 .is_some_and(|m| matches!(m, ListeningMode::EventsOnly(_)));
3291 if !is_events_only {
3292 match Box::pin(await_while_polling(
3293 this.update_notification_streams(&mut senders, &mut circuit_breakers)
3294 .fuse(),
3295 &mut process_notifications,
3296 ))
3297 .await
3298 {
3299 Ok(handler) => process_notifications.push(handler),
3300 Err(error) => error!("Failed to update committee: {error}"),
3301 }
3302 }
3303 }
3304 }
3305
3306 for abort in senders.into_values() {
3307 abort.abort();
3308 }
3309
3310 let () = process_notifications.collect().await;
3311 }
3312 .in_current_span();
3313
3314 Ok((update_streams, AbortOnDrop(abort), notifications))
3315 }
3316
3317 #[instrument(level = "trace", skip(senders, circuit_breakers))]
3318 async fn update_notification_streams(
3319 &self,
3320 senders: &mut HashMap<ValidatorPublicKey, AbortHandle>,
3321 circuit_breakers: &mut HashMap<ValidatorPublicKey, CircuitBreakerState>,
3322 ) -> Result<impl Future<Output = ()>, Error> {
3323 let initial_probe_interval = self
3324 .options
3325 .notification_circuit_breaker_initial_probe_interval;
3326 let max_probe_interval = self.options.notification_circuit_breaker_max_probe_interval;
3327 let now = self.storage_client().clock().current_time();
3330
3331 let events_only = self
3332 .listening_mode()
3333 .is_none_or(|m| matches!(m, ListeningMode::EventsOnly(_)));
3334 let (nodes, local_node) = {
3335 let committee = if events_only {
3339 let (_, committee) = self.admin_committee().await?;
3340 committee
3341 } else {
3342 self.local_committee().await?
3343 };
3344 let nodes = self
3345 .client
3346 .validator_node_provider()
3347 .make_nodes(&committee)?
3348 .collect::<HashMap<_, _>>();
3349 (nodes, self.client.local_node.clone())
3350 };
3351
3352 for (validator, abort) in senders.iter() {
3354 if abort.is_aborted() && nodes.contains_key(validator) {
3355 if let Some(state) = circuit_breakers.get_mut(validator) {
3356 state.probe_interval = (state.probe_interval * 2).min(max_probe_interval);
3358 state.next_probe_at =
3359 now.saturating_add(TimeDelta::from_duration(state.probe_interval));
3360 warn!(
3361 %validator,
3362 chain_id = %self.chain_id,
3363 next_probe_in = ?state.probe_interval,
3364 "Validator still unhealthy after probe; increasing probe interval"
3365 );
3366 } else {
3367 circuit_breakers.insert(
3369 *validator,
3370 CircuitBreakerState {
3371 next_probe_at: now
3372 .saturating_add(TimeDelta::from_duration(initial_probe_interval)),
3373 probe_interval: initial_probe_interval,
3374 },
3375 );
3376 error!(
3377 %validator,
3378 chain_id = %self.chain_id,
3379 next_probe_in = ?initial_probe_interval,
3380 "Validator notification stream ended; entering circuit breaker"
3381 );
3382 }
3383 } else if !abort.is_aborted() && circuit_breakers.contains_key(validator) {
3384 info!(
3386 %validator,
3387 chain_id = %self.chain_id,
3388 "Validator recovered from circuit breaker"
3389 );
3390 circuit_breakers.remove(validator);
3391 }
3392 }
3393
3394 senders.retain(|validator, abort| {
3395 if !nodes.contains_key(validator) {
3396 abort.abort();
3397 }
3398 !abort.is_aborted()
3399 });
3400 circuit_breakers.retain(|validator, _| nodes.contains_key(validator));
3401
3402 let validator_tasks = FuturesUnordered::new();
3403 for (public_key, node) in nodes {
3404 let hash_map::Entry::Vacant(entry) = senders.entry(public_key) else {
3405 continue;
3406 };
3407
3408 if let Some(state) = circuit_breakers.get(&public_key) {
3410 if now < state.next_probe_at {
3411 continue;
3412 }
3413 debug!(
3414 validator = %public_key,
3415 chain_id = %self.chain_id,
3416 "Probing unhealthy validator"
3417 );
3418 }
3419
3420 let address = node.address();
3421 let this = self.clone();
3422 let stream = stream::once({
3423 let node = node.clone();
3424 async move {
3425 let stream = node.subscribe(vec![this.chain_id]).await?;
3426 if !events_only {
3429 let remote_node = RemoteNode { public_key, node };
3430 this.client
3431 .synchronize_chain_state_from(&remote_node, this.chain_id)
3432 .await?;
3433 } else {
3434 let remote_node = RemoteNode { public_key, node };
3438 if let Some(ListeningMode::EventsOnly(subscribed)) = this.listening_mode() {
3439 if let Err(error) = this
3440 .client
3441 .sync_events_from_node(this.chain_id, &subscribed, &remote_node)
3442 .await
3443 {
3444 debug!(
3445 chain_id = %this.chain_id,
3446 %error,
3447 "Failed initial sparse sync for EventsOnly chain"
3448 );
3449 }
3450 }
3451 }
3452 Ok::<_, Error>(stream)
3453 }
3454 })
3455 .filter_map(move |result| {
3456 let address = address.clone();
3457 async move {
3458 if let Err(error) = &result {
3459 info!(?error, address, "could not connect to validator");
3460 } else {
3461 debug!(address, "connected to validator");
3462 }
3463 result.ok()
3464 }
3465 })
3466 .flatten();
3467 let (stream, abort) = stream::abortable(stream);
3468 let abort_on_exit = abort.clone();
3469 let mut stream = Box::pin(stream);
3470 let this = self.clone();
3471 let local_node = local_node.clone();
3472 let remote_node = RemoteNode { public_key, node };
3473 validator_tasks.push(async move {
3474 while let Some(notification) = stream.next().await {
3475 if let Err(error) = this
3476 .process_notification(
3477 remote_node.clone(),
3478 local_node.clone(),
3479 notification.clone(),
3480 )
3481 .await
3482 {
3483 tracing::info!(
3484 chain_id = %this.chain_id,
3485 address = remote_node.address(),
3486 ?notification,
3487 %error,
3488 "failed to process notification",
3489 );
3490 }
3491 }
3492 warn!(
3493 chain_id = %this.chain_id,
3494 address = remote_node.address(),
3495 "Validator notification stream ended"
3496 );
3497 abort_on_exit.abort();
3498 });
3499 entry.insert(abort);
3500 }
3501 Ok(validator_tasks.collect())
3502 }
3503
3504 #[instrument(level = "trace", skip(remote_node))]
3506 pub async fn sync_validator(&self, remote_node: Env::ValidatorNode) -> Result<(), Error> {
3507 let validator_next_block_height = match remote_node
3508 .handle_chain_info_query(ChainInfoQuery::new(self.chain_id))
3509 .await
3510 {
3511 Ok(info) => info.info.next_block_height,
3512 Err(NodeError::BlobsNotFound(_)) => BlockHeight::ZERO,
3514 Err(err) => return Err(err.into()),
3515 };
3516 let local_next_block_height = self.chain_info().await?.next_block_height;
3517
3518 if validator_next_block_height >= local_next_block_height {
3519 debug!("Validator is up-to-date with local state");
3520 return Ok(());
3521 }
3522
3523 let heights = (validator_next_block_height.0..local_next_block_height.0)
3524 .map(BlockHeight)
3525 .collect::<Vec<_>>();
3526
3527 let certificates = self
3528 .client
3529 .storage_client()
3530 .read_certificates_by_heights(self.chain_id, &heights)
3531 .await?
3532 .into_iter()
3533 .flatten();
3534
3535 for certificate in certificates {
3536 let missing_blob_ids = match remote_node
3537 .handle_confirmed_certificate(
3538 certificate.clone(),
3539 CrossChainMessageDelivery::NonBlocking,
3540 )
3541 .await
3542 {
3543 Ok(_) => continue,
3544 Err(NodeError::BlobsNotFound(missing_blob_ids)) => missing_blob_ids,
3545 Err(err) => return Err(err.into()),
3546 };
3547 let missing_blobs = self
3550 .client
3551 .storage_client()
3552 .read_blobs(&missing_blob_ids)
3553 .await?
3554 .into_iter()
3555 .flatten()
3556 .map(|b| b.into_std())
3557 .collect();
3558 remote_node.upload_blobs(missing_blobs).await?;
3559 remote_node
3560 .handle_confirmed_certificate(certificate, CrossChainMessageDelivery::NonBlocking)
3561 .await?;
3562 }
3563
3564 Ok(())
3565 }
3566}
3567
3568#[cfg(with_testing)]
3569impl<Env: Environment> ChainClient<Env> {
3570 pub async fn process_notification_from(
3572 &self,
3573 notification: Notification,
3574 validator: (ValidatorPublicKey, &str),
3575 ) {
3576 let mut node_list = self
3577 .client
3578 .validator_node_provider()
3579 .make_nodes_from_list(vec![validator])
3580 .unwrap();
3581 let (public_key, node) = node_list.next().unwrap();
3582 let remote_node = RemoteNode { node, public_key };
3583 let local_node = self.client.local_node.clone();
3584 self.process_notification(remote_node, local_node, notification)
3585 .await
3586 .unwrap();
3587 }
3588}
3589
3590#[cfg(test)]
3591mod tests {
3592 use super::{Error, LocalNodeError};
3593
3594 #[test]
3595 fn error_type_delegates_to_local_node_error() {
3596 assert_eq!(
3597 Error::LocalNodeError(LocalNodeError::InvalidChainInfoResponse).error_type(),
3598 "LocalNodeError::InvalidChainInfoResponse"
3599 );
3600 }
3601
3602 #[test]
3603 fn error_type_falls_back_to_chain_client_variant() {
3604 assert_eq!(
3605 Error::WalletSynchronizationError.error_type(),
3606 "ChainClientError::WalletSynchronizationError"
3607 );
3608 }
3609}