1use std::{
6 collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
7 pin,
8 sync::{Arc, Mutex, RwLock},
9 time::Duration,
10};
11
12use futures::{
13 future::{self, Either, Shared, WeakShared},
14 FutureExt as _,
15};
16use linera_base::{
17 crypto::{CryptoError, CryptoHash, ValidatorPublicKey},
18 data_types::{
19 ApplicationDescription, ArithmeticError, Blob, BlockHeight, Epoch, Round, TimeDelta,
20 Timestamp,
21 },
22 doc_scalar,
23 identifiers::{AccountOwner, ApplicationId, BlobId, ChainId, EventId, StreamId},
24};
25use linera_cache::{Arc as CacheArc, UniqueValueCache, ValueCache, DEFAULT_CLEANUP_INTERVAL_SECS};
26#[cfg(with_testing)]
27use linera_chain::ChainExecutionContext;
28use linera_chain::{
29 data_types::{BlockProposal, BundleExecutionPolicy, MessageBundle, ProposedBlock},
30 types::{
31 Block, CertificateValue, Certified, ConfirmedBlock, ConfirmedBlockCertificate,
32 GenericCertificate, LiteCertificate, Timeout, TimeoutCertificate, ValidatedBlock,
33 ValidatedBlockCertificate,
34 },
35 ChainError, ChainStateView, StreamCounts,
36};
37use linera_execution::{ExecutionError, ExecutionStateView, Query, QueryOutcome, ResourceTracker};
38use linera_storage::{Clock as _, Storage};
39use linera_views::{context::InactiveContext, ViewError};
40use serde::{Deserialize, Serialize};
41use thiserror::Error;
42use tokio::sync::{mpsc, oneshot, OwnedRwLockReadGuard};
43use tracing::{debug, instrument, trace, warn};
44
45pub struct ChainStateViewReadGuard<S: Storage>(
52 OwnedRwLockReadGuard<ChainWorkerState<S>, ChainStateView<S::Context>>,
53);
54
55impl<S: Storage> std::ops::Deref for ChainStateViewReadGuard<S> {
56 type Target = ChainStateView<S::Context>;
57
58 fn deref(&self) -> &Self::Target {
59 &self.0
60 }
61}
62
63pub(crate) use crate::chain_worker::EventSubscriptionsResult;
65use crate::{
66 chain_worker::{
67 handle, state::ChainWorkerState, BlockExportHandle, BlockOutcome, ChainWorkerConfig,
68 CrossChainUpdateResult, DeliveryNotifier, ProcessConfirmedBlockMode,
69 },
70 client::{ChainModes, ListeningMode},
71 data_types::{ChainInfoQuery, ChainInfoResponse, CrossChainRequest},
72 notifier::Notifier,
73};
74
75pub const DEFAULT_BLOCK_CACHE_SIZE: usize = 5_000;
77pub const DEFAULT_EXECUTION_STATE_CACHE_SIZE: usize = 10_000;
79
80#[cfg(test)]
81#[path = "unit_tests/worker_tests.rs"]
82mod worker_tests;
83
84#[cfg(all(test, feature = "rocksdb"))]
85#[path = "unit_tests/worker_backup_tests.rs"]
86mod worker_backup_tests;
87
88#[cfg(not(web))]
91pub(crate) fn wrap_future<F: std::future::Future>(f: F) -> sync_wrapper::SyncFuture<F> {
92 sync_wrapper::SyncFuture::new(f)
93}
94
95#[cfg(web)]
98pub(crate) fn wrap_future<F: std::future::Future>(f: F) -> F {
99 f
100}
101
102#[cfg(with_metrics)]
103pub(crate) mod metrics {
104 use linera_base::prometheus_util::{
105 exponential_bucket_interval, register_histogram, register_histogram_vec,
106 register_int_counter, register_int_counter_vec,
107 };
108 use linera_chain::{data_types::MessageAction, types::ConfirmedBlockCertificate};
109 use prometheus::{Histogram, HistogramVec, IntCounter, IntCounterVec};
110
111 pub struct MetricsData {
113 certificate_log_str: &'static str,
114 round_type: &'static str,
115 round_number: u32,
116 confirmed_transactions: u64,
117 confirmed_incoming_bundles: u64,
118 confirmed_rejected_bundles: u64,
119 confirmed_incoming_messages: u64,
120 confirmed_operations: u64,
121 validators_with_signatures: Vec<String>,
122 }
123
124 impl MetricsData {
125 pub fn new(certificate: &ConfirmedBlockCertificate) -> Self {
127 Self {
128 certificate_log_str: certificate.inner().to_log_str(),
129 round_type: certificate.round.type_name(),
130 round_number: certificate.round.number(),
131 confirmed_transactions: certificate.block().body.transactions.len() as u64,
132 confirmed_incoming_bundles: certificate.block().body.incoming_bundles().count()
133 as u64,
134 confirmed_rejected_bundles: certificate
135 .block()
136 .body
137 .incoming_bundles()
138 .filter(|b| b.action == MessageAction::Reject)
139 .count() as u64,
140 confirmed_incoming_messages: certificate
141 .block()
142 .body
143 .incoming_bundles()
144 .map(|b| b.messages().count())
145 .sum::<usize>() as u64,
146 confirmed_operations: certificate.block().body.operations().count() as u64,
147 validators_with_signatures: certificate
148 .signatures()
149 .iter()
150 .map(|(validator_name, _)| validator_name.to_string())
151 .collect(),
152 }
153 }
154
155 pub fn record(self) {
157 NUM_BLOCKS.with_label_values(&[]).inc();
158 NUM_ROUNDS_IN_CERTIFICATE
159 .with_label_values(&[self.certificate_log_str, self.round_type])
160 .observe(self.round_number as f64);
161 TRANSACTIONS_PER_BLOCK.observe(self.confirmed_transactions as f64);
162 INCOMING_BUNDLES_PER_BLOCK.observe(self.confirmed_incoming_bundles as f64);
163 OPERATIONS_PER_BLOCK.observe(self.confirmed_operations as f64);
164 if self.confirmed_transactions > 0 {
165 TRANSACTION_COUNT
166 .with_label_values(&[])
167 .inc_by(self.confirmed_transactions);
168 if self.confirmed_incoming_bundles > 0 {
169 INCOMING_BUNDLE_COUNT.inc_by(self.confirmed_incoming_bundles);
170 }
171 if self.confirmed_rejected_bundles > 0 {
172 REJECTED_BUNDLE_COUNT.inc_by(self.confirmed_rejected_bundles);
173 }
174 if self.confirmed_incoming_messages > 0 {
175 INCOMING_MESSAGE_COUNT.inc_by(self.confirmed_incoming_messages);
176 }
177 if self.confirmed_operations > 0 {
178 OPERATION_COUNT.inc_by(self.confirmed_operations);
179 }
180 }
181
182 for validator_name in self.validators_with_signatures {
183 CERTIFICATES_SIGNED
184 .with_label_values(&[&validator_name])
185 .inc();
186 }
187 }
188 }
189
190 linera_base::declare_metrics! {
191 pub static NUM_ROUNDS_IN_CERTIFICATE: HistogramVec =
192 register_histogram_vec(
193 "num_rounds_in_certificate",
194 "Number of rounds in certificate",
195 &["certificate_value", "round_type"],
196 exponential_bucket_interval(0.1, 50.0),
197 );
198
199 pub static NUM_ROUNDS_IN_BLOCK_PROPOSAL: HistogramVec =
200 register_histogram_vec(
201 "num_rounds_in_block_proposal",
202 "Number of rounds in block proposal",
203 &["round_type"],
204 exponential_bucket_interval(0.1, 50.0),
205 );
206
207 pub static TRANSACTION_COUNT: IntCounterVec =
208 register_int_counter_vec("transaction_count", "Transaction count", &[]);
209
210 pub static INCOMING_BUNDLE_COUNT: IntCounter =
211 register_int_counter("incoming_bundle_count", "Incoming bundle count");
212
213 pub static REJECTED_BUNDLE_COUNT: IntCounter =
214 register_int_counter("rejected_bundle_count", "Rejected bundle count");
215
216 pub static INCOMING_MESSAGE_COUNT: IntCounter =
217 register_int_counter("incoming_message_count", "Incoming message count");
218
219 pub static OPERATION_COUNT: IntCounter =
220 register_int_counter("operation_count", "Operation count");
221
222 pub static OPERATIONS_PER_BLOCK: Histogram =
223 register_histogram(
224 "operations_per_block",
225 "Number of operations per block",
226 exponential_bucket_interval(1.0, 10000.0),
227 );
228
229 pub static INCOMING_BUNDLES_PER_BLOCK: Histogram =
230 register_histogram(
231 "incoming_bundles_per_block",
232 "Number of incoming bundles per block",
233 exponential_bucket_interval(1.0, 10000.0),
234 );
235
236 pub static TRANSACTIONS_PER_BLOCK: Histogram =
237 register_histogram(
238 "transactions_per_block",
239 "Number of transactions per block",
240 exponential_bucket_interval(1.0, 10000.0),
241 );
242
243 pub static NUM_BLOCKS: IntCounterVec =
244 register_int_counter_vec("num_blocks", "Number of blocks added to chains", &[]);
245
246 pub static CERTIFICATES_SIGNED: IntCounterVec =
247 register_int_counter_vec(
248 "certificates_signed",
249 "Number of confirmed block certificates signed by each validator",
250 &["validator_name"],
251 );
252
253 pub static CHAIN_INFO_QUERIES: IntCounter =
254 register_int_counter(
255 "chain_info_queries",
256 "Number of chain info queries processed",
257 );
258
259 pub static CROSS_CHAIN_BATCH_SIZE: Histogram =
260 register_histogram(
261 "cross_chain_batch_size",
262 "Number of cross-chain requests coalesced into a single per-chain batch",
263 exponential_bucket_interval(1.0, 1000.0),
264 );
265 }
266}
267
268#[derive(Default, Debug)]
270pub struct NetworkActions {
271 pub cross_chain_requests: Vec<CrossChainRequest>,
273 pub notifications: Vec<Notification>,
275}
276
277impl NetworkActions {
278 pub fn extend(&mut self, other: NetworkActions) {
280 self.cross_chain_requests.extend(other.cross_chain_requests);
281 self.notifications.extend(other.notifications);
282 }
283}
284
285#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
286#[allow(missing_docs)]
288pub struct Notification {
289 pub chain_id: ChainId,
290 pub reason: Reason,
291}
292
293doc_scalar!(
294 Notification,
295 "Notify that a chain has a new certified block or a new message"
296);
297
298#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
299#[allow(missing_docs)]
301pub enum Reason {
302 NewBlock {
303 height: BlockHeight,
304 hash: CryptoHash,
305 },
306 NewEvents {
307 height: BlockHeight,
308 block_hash: CryptoHash,
309 event_streams: BTreeSet<StreamId>,
310 },
311 NewIncomingBundle {
312 origin: ChainId,
313 height: BlockHeight,
314 },
315 NewRound {
316 height: BlockHeight,
317 round: Round,
318 },
319 BlockExecuted {
320 height: BlockHeight,
321 hash: CryptoHash,
322 },
323}
324
325#[derive(Debug, Error, strum::IntoStaticStr)]
327#[allow(missing_docs)]
328pub enum WorkerError {
329 #[error(transparent)]
330 CryptoError(#[from] CryptoError),
331
332 #[error(transparent)]
333 ArithmeticError(#[from] ArithmeticError),
334
335 #[error(transparent)]
336 ViewError(#[from] ViewError),
337
338 #[error("Certificates referenced from chain state are missing in storage: {0:?}")]
339 ReadCertificatesError(Vec<CryptoHash>),
340
341 #[error(transparent)]
342 ChainError(#[from] Box<ChainError>),
343
344 #[error(transparent)]
345 BcsError(#[from] bcs::Error),
346
347 #[error("Block was not signed by an authorized owner")]
349 InvalidOwner,
350
351 #[error("Operations in the block are not authenticated by the proper owner: {0}")]
352 InvalidSigner(AccountOwner),
353
354 #[error(
356 "Chain is expecting a next block at height {expected_block_height} but the given block \
357 is at height {found_block_height} instead"
358 )]
359 UnexpectedBlockHeight {
360 expected_block_height: BlockHeight,
361 found_block_height: BlockHeight,
362 },
363 #[error("Unexpected epoch {epoch}: chain {chain_id} is at {chain_epoch}")]
364 InvalidEpoch {
365 chain_id: ChainId,
366 chain_epoch: Epoch,
367 epoch: Epoch,
368 },
369
370 #[error("Events not found: {0:?}")]
371 EventsNotFound(Vec<EventId>),
372
373 #[error("Invalid cross-chain request")]
375 InvalidCrossChainRequest,
376 #[error("The block does not contain the hash that we expected for the previous block")]
377 InvalidBlockChaining,
378 #[error(
379 "Block timestamp ({block_timestamp}) is further in the future from local time \
380 ({local_time}) than block time grace period ({block_time_grace_period:?})"
381 )]
382 InvalidTimestamp {
383 block_timestamp: Timestamp,
384 local_time: Timestamp,
385 block_time_grace_period: Duration,
386 },
387 #[error("We don't have the value for the certificate.")]
388 MissingCertificateValue,
389 #[error("The hash certificate doesn't match its value.")]
390 InvalidLiteCertificate,
391 #[error("Fast blocks cannot query oracles")]
392 FastBlockUsingOracles,
393 #[error("Blobs not found: {0:?}")]
394 BlobsNotFound(Vec<BlobId>),
395 #[error("Blocks not found: {0:?}")]
402 BlocksNotFound(Vec<CryptoHash>),
403 #[error("Block hash at height {height} for chain {chain_id} not found")]
404 BlockHashNotFound {
405 height: BlockHeight,
406 chain_id: ChainId,
407 },
408 #[error("Block at height {height} on chain {chain_id} not found in local storage")]
409 LocalBlockNotFound {
410 height: BlockHeight,
411 chain_id: ChainId,
412 },
413 #[error("The block proposal is invalid: {0}")]
414 InvalidBlockProposal(String),
415 #[error("Blob was not required by any pending block")]
416 UnexpectedBlob,
417 #[error("Number of published blobs per block must not exceed {0}")]
418 TooManyPublishedBlobs(u64),
419 #[error("Missing network description")]
420 MissingNetworkDescription,
421 #[error("thread error: {0}")]
422 Thread(#[from] web_thread_pool::Error),
423 #[error("Chain worker was poisoned by a failed save that may have left storage in an undetermined state")]
424 PoisonedWorker,
425 #[error("Cross-chain batch was rolled back due to an error in another request")]
426 BatchRolledBack,
427}
428
429impl WorkerError {
430 pub fn is_local(&self) -> bool {
434 match self {
435 WorkerError::CryptoError(_)
436 | WorkerError::ArithmeticError(_)
437 | WorkerError::InvalidOwner
438 | WorkerError::InvalidSigner(_)
439 | WorkerError::UnexpectedBlockHeight { .. }
440 | WorkerError::InvalidEpoch { .. }
441 | WorkerError::EventsNotFound(_)
442 | WorkerError::InvalidBlockChaining
443 | WorkerError::InvalidTimestamp { .. }
444 | WorkerError::MissingCertificateValue
445 | WorkerError::InvalidLiteCertificate
446 | WorkerError::FastBlockUsingOracles
447 | WorkerError::BlobsNotFound(_)
448 | WorkerError::BlocksNotFound(_)
449 | WorkerError::InvalidBlockProposal(_)
450 | WorkerError::UnexpectedBlob
451 | WorkerError::TooManyPublishedBlobs(_)
452 | WorkerError::ViewError(ViewError::NotFound(_)) => false,
453 WorkerError::BcsError(_)
454 | WorkerError::InvalidCrossChainRequest
455 | WorkerError::ViewError(_)
456 | WorkerError::BlockHashNotFound { .. }
457 | WorkerError::LocalBlockNotFound { .. }
458 | WorkerError::MissingNetworkDescription
459 | WorkerError::Thread(_)
460 | WorkerError::ReadCertificatesError(_)
461 | WorkerError::PoisonedWorker
462 | WorkerError::BatchRolledBack => true,
463 WorkerError::ChainError(chain_error) => chain_error.is_local(),
464 }
465 }
466
467 pub fn error_type(&self) -> String {
473 match self {
474 WorkerError::ChainError(chain_error) => chain_error.error_type(),
475 other => {
476 let variant: &'static str = other.into();
477 format!("WorkerError::{variant}")
478 }
479 }
480 }
481
482 pub(crate) fn must_reload_view(&self) -> bool {
485 matches!(
486 self,
487 WorkerError::PoisonedWorker
488 | WorkerError::ViewError(ViewError::StoreError {
489 must_reload_view: true,
490 ..
491 })
492 )
493 }
494
495 pub(crate) fn indicates_corrupted_chain_state(&self) -> bool {
499 matches!(
500 self,
501 WorkerError::ChainError(chain_error)
502 if matches!(chain_error.as_ref(), ChainError::CorruptedChainState(_))
503 )
504 }
505}
506
507impl From<ChainError> for WorkerError {
508 #[instrument(level = "trace", skip(chain_error))]
509 fn from(chain_error: ChainError) -> Self {
510 match chain_error {
511 ChainError::ExecutionError(execution_error, context) => match *execution_error {
512 ExecutionError::BlobsNotFound(blob_ids) => Self::BlobsNotFound(blob_ids),
513 ExecutionError::EventsNotFound(event_ids) => Self::EventsNotFound(event_ids),
514 _ => Self::ChainError(Box::new(ChainError::ExecutionError(
515 execution_error,
516 context,
517 ))),
518 },
519 error => Self::ChainError(Box::new(error)),
520 }
521 }
522}
523
524#[cfg(with_testing)]
525impl WorkerError {
526 pub fn expect_execution_error(self, expected_context: ChainExecutionContext) -> ExecutionError {
532 let WorkerError::ChainError(chain_error) = self else {
533 panic!("Expected an `ExecutionError`. Got: {self:#?}");
534 };
535
536 let ChainError::ExecutionError(execution_error, context) = *chain_error else {
537 panic!("Expected an `ExecutionError`. Got: {chain_error:#?}");
538 };
539
540 assert_eq!(context, expected_context);
541
542 *execution_error
543 }
544}
545
546type ChainWorkerArc<S> = Arc<tokio::sync::RwLock<ChainWorkerState<S>>>;
547type ChainWorkerWeak<S> = std::sync::Weak<tokio::sync::RwLock<ChainWorkerState<S>>>;
548type ChainWorkerFuture<S> = Shared<oneshot::Receiver<ChainWorkerWeak<S>>>;
549
550type ChainWorkerMap<S> = Arc<papaya::HashMap<ChainId, ChainWorkerFuture<S>>>;
558
559pub(crate) enum BatchRequest {
561 Update {
562 origin: ChainId,
563 bundles: Vec<(Epoch, MessageBundle)>,
564 previous_height: Option<BlockHeight>,
565 result_sender: oneshot::Sender<Result<CrossChainUpdateResult, WorkerError>>,
566 },
567 Confirm {
568 recipient: ChainId,
569 latest_height: BlockHeight,
570 result_sender: oneshot::Sender<Result<NetworkActions, WorkerError>>,
571 },
572}
573
574type BatchFuture = crate::MaybeSendBoxFuture<'static, ()>;
582
583#[derive(Clone)]
584struct ChainBatchRequestProcessor {
585 sender: mpsc::UnboundedSender<BatchRequest>,
587 future: WeakShared<BatchFuture>,
590}
591
592impl ChainBatchRequestProcessor {
593 fn create<StorageClient>(
594 worker: WorkerState<StorageClient>,
595 chain_id: ChainId,
596 batch_size_limit: usize,
597 ) -> (ChainBatchRequestProcessor, Shared<BatchFuture>)
598 where
599 StorageClient: Storage + Clone + 'static,
600 {
601 let (sender, mut receiver) = mpsc::unbounded_channel();
602 let future: BatchFuture = Box::pin(async move {
603 while let Some(first) = receiver.recv().await {
604 let mut requests = vec![first];
605 while requests.len() < batch_size_limit {
606 match receiver.try_recv() {
607 Ok(request) => requests.push(request),
608 Err(_) => break,
609 }
610 }
611 #[cfg(with_metrics)]
612 metrics::CROSS_CHAIN_BATCH_SIZE.observe(requests.len() as f64);
613 if let Err(error) = worker
624 .chain_write(chain_id, move |mut guard| async move {
625 guard.process_batch(requests).await
626 })
627 .await
628 {
629 tracing::warn!(%chain_id, %error, "cross-chain batch could not be processed");
630 }
631 }
632 });
633 let shared = future.shared();
634 let weak = shared.downgrade().expect("future has not been polled yet");
635 let batch_processor = ChainBatchRequestProcessor {
636 sender,
637 future: weak,
638 };
639 (batch_processor, shared)
640 }
641}
642
643type ChainBatchMap = Arc<papaya::HashMap<ChainId, ChainBatchRequestProcessor>>;
644
645fn start_sweep<S: Storage + Clone + 'static>(
649 chain_workers: &ChainWorkerMap<S>,
650 config: &ChainWorkerConfig,
651) {
652 let interval = match (config.ttl, config.sender_chain_ttl) {
655 (None, None) => return,
656 (Some(d), None) | (None, Some(d)) => d,
657 (Some(a), Some(b)) => a.min(b),
658 };
659 let weak_map = Arc::downgrade(chain_workers);
660 linera_base::Task::spawn(async move {
661 loop {
662 linera_base::time::timer::sleep(interval).await;
663 let Some(map) = weak_map.upgrade() else {
664 break;
665 };
666 map.pin_owned().retain(|_, shared| match shared.peek() {
667 Some(Ok(weak)) => weak.strong_count() > 0,
668 Some(Err(_)) => false, None => true, });
671 }
672 })
673 .forget();
674}
675
676pub struct WorkerState<StorageClient: Storage> {
678 storage: StorageClient,
680 chain_worker_config: ChainWorkerConfig,
682 block_cache: Arc<ValueCache<CryptoHash, ConfirmedBlock>>,
683 execution_state_cache:
684 Option<Arc<UniqueValueCache<CryptoHash, ExecutionStateView<InactiveContext>>>>,
685 pub(crate) chain_modes: Option<Arc<RwLock<ChainModes>>>,
687 delivery_notifiers: Arc<Mutex<DeliveryNotifiers>>,
690 chain_workers: ChainWorkerMap<StorageClient>,
694 chain_batches: ChainBatchMap,
696 outbound_cross_chain_sender: Option<OutboundCrossChainSender>,
702 block_export: Option<BlockExportHandle>,
705}
706
707pub type OutboundCrossChainSender = Arc<dyn Fn(CrossChainRequest) + Send + Sync>;
710
711impl<StorageClient> Clone for WorkerState<StorageClient>
712where
713 StorageClient: Storage + Clone,
714{
715 fn clone(&self) -> Self {
716 WorkerState {
717 storage: self.storage.clone(),
718 chain_worker_config: self.chain_worker_config.clone(),
719 block_cache: self.block_cache.clone(),
720 execution_state_cache: self.execution_state_cache.clone(),
721 chain_modes: self.chain_modes.clone(),
722 delivery_notifiers: self.delivery_notifiers.clone(),
723 chain_workers: self.chain_workers.clone(),
724 chain_batches: self.chain_batches.clone(),
725 outbound_cross_chain_sender: self.outbound_cross_chain_sender.clone(),
726 block_export: self.block_export.clone(),
727 }
728 }
729}
730
731pub(crate) type DeliveryNotifiers = HashMap<ChainId, DeliveryNotifier>;
732
733impl<StorageClient> WorkerState<StorageClient>
734where
735 StorageClient: Storage,
736{
737 #[cfg(with_testing)]
739 #[instrument(level = "trace", skip(self))]
740 pub fn with_cross_chain_message_chunk_limit(mut self, limit: usize) -> Self {
741 self.chain_worker_config.cross_chain_message_chunk_limit = limit;
742 self
743 }
744
745 #[cfg(with_testing)]
747 pub fn set_cross_chain_message_chunk_limit(&mut self, limit: usize) {
748 self.chain_worker_config.cross_chain_message_chunk_limit = limit;
749 }
750
751 #[cfg(with_testing)]
753 #[instrument(level = "trace", skip(self, value))]
754 pub fn with_allow_revert_confirm(mut self, value: bool) -> Self {
755 self.chain_worker_config.allow_revert_confirm = value;
756 self
757 }
758
759 #[instrument(level = "trace", skip(self))]
761 pub fn nickname(&self) -> &str {
762 &self.chain_worker_config.nickname
763 }
764
765 #[instrument(level = "trace", skip(self))]
767 #[cfg(not(feature = "test"))]
768 pub(crate) fn storage_client(&self) -> &StorageClient {
769 &self.storage
770 }
771
772 #[instrument(level = "trace", skip(self))]
775 #[cfg(feature = "test")]
776 pub fn storage_client(&self) -> &StorageClient {
777 &self.storage
778 }
779
780 #[instrument(level = "trace", skip(self, certificate))]
781 pub(crate) async fn full_certificate(
782 &self,
783 certificate: LiteCertificate<'_>,
784 ) -> Result<Either<ConfirmedBlockCertificate, ValidatedBlockCertificate>, WorkerError> {
785 let block = self
786 .block_cache
787 .get(&certificate.value.value_hash)
788 .ok_or(WorkerError::MissingCertificateValue)?;
789 let block = CacheArc::unwrap_or_clone(block);
790
791 match certificate.value.kind {
792 linera_chain::types::CertificateKind::Confirmed => Ok(Either::Left(
793 certificate
794 .into_confirmed_certificate(block)
795 .ok_or(WorkerError::InvalidLiteCertificate)?,
796 )),
797 linera_chain::types::CertificateKind::Validated => {
798 let value = ValidatedBlock::from_hashed(block.into_inner());
799 Ok(Either::Right(
800 certificate
801 .into_validated_certificate(value)
802 .ok_or(WorkerError::InvalidLiteCertificate)?,
803 ))
804 }
805 _ => Err(WorkerError::InvalidLiteCertificate),
806 }
807 }
808}
809
810#[allow(async_fn_in_trait)]
811#[cfg_attr(not(web), trait_variant::make(Send))]
812pub trait ProcessableCertificate: CertificateValue + Sized + 'static {
814 type Certificate: Certified<Value = Self> + Clone + Send + Sync + 'static;
817
818 fn make_certificate(
821 quorum: GenericCertificate<Self>,
822 justification: linera_chain::justification::JustificationChain,
823 ) -> Self::Certificate;
824
825 async fn process_certificate<S: Storage + Clone + 'static>(
827 worker: &WorkerState<S>,
828 certificate: Self::Certificate,
829 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError>;
830}
831
832impl ProcessableCertificate for ConfirmedBlock {
833 type Certificate = ConfirmedBlockCertificate;
834
835 fn make_certificate(
836 quorum: GenericCertificate<Self>,
837 justification: linera_chain::justification::JustificationChain,
838 ) -> Self::Certificate {
839 ConfirmedBlockCertificate::from_parts(quorum, justification)
840 }
841
842 async fn process_certificate<S: Storage + Clone + 'static>(
843 worker: &WorkerState<S>,
844 certificate: ConfirmedBlockCertificate,
845 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
846 Box::pin(worker.handle_confirmed_certificate(
847 certificate,
848 ProcessConfirmedBlockMode::Auto,
849 None,
850 ))
851 .await
852 }
853}
854
855impl ProcessableCertificate for ValidatedBlock {
856 type Certificate = ValidatedBlockCertificate;
857
858 fn make_certificate(
859 quorum: GenericCertificate<Self>,
860 justification: linera_chain::justification::JustificationChain,
861 ) -> Self::Certificate {
862 ValidatedBlockCertificate::from_parts(quorum, justification)
863 }
864
865 async fn process_certificate<S: Storage + Clone + 'static>(
866 worker: &WorkerState<S>,
867 certificate: ValidatedBlockCertificate,
868 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
869 Box::pin(worker.handle_validated_certificate(certificate)).await
870 }
871}
872
873impl ProcessableCertificate for Timeout {
874 type Certificate = TimeoutCertificate;
875
876 fn make_certificate(
877 quorum: GenericCertificate<Self>,
878 _justification: linera_chain::justification::JustificationChain,
879 ) -> Self::Certificate {
880 quorum
881 }
882
883 async fn process_certificate<S: Storage + Clone + 'static>(
884 worker: &WorkerState<S>,
885 certificate: TimeoutCertificate,
886 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
887 worker.handle_timeout_certificate(certificate).await
888 }
889}
890
891impl<StorageClient> WorkerState<StorageClient>
892where
893 StorageClient: Storage + Clone + 'static,
894{
895 #[instrument(level = "trace", skip(storage, chain_worker_config))]
900 pub fn new(
901 storage: StorageClient,
902 chain_worker_config: ChainWorkerConfig,
903 chain_modes: Option<Arc<RwLock<ChainModes>>>,
904 ) -> Self {
905 let chain_workers = Arc::new(papaya::HashMap::new());
906 start_sweep(&chain_workers, &chain_worker_config);
907 let block_cache_size = chain_worker_config.block_cache_size;
908 let execution_state_cache_size = chain_worker_config.execution_state_cache_size;
909 WorkerState {
910 storage,
911 chain_worker_config,
912 block_cache: Arc::new(ValueCache::new(
913 "worker_block",
914 block_cache_size,
915 DEFAULT_CLEANUP_INTERVAL_SECS,
916 )),
917 execution_state_cache: (execution_state_cache_size > 0)
918 .then(|| Arc::new(UniqueValueCache::new(execution_state_cache_size))),
919 chain_modes,
920 delivery_notifiers: Arc::default(),
921 chain_workers,
922 #[cfg_attr(web, expect(clippy::arc_with_non_send_sync))]
926 chain_batches: Arc::new(papaya::HashMap::new()),
927 outbound_cross_chain_sender: None,
928 block_export: None,
929 }
930 }
931
932 pub fn with_block_export(mut self, handle: BlockExportHandle) -> Self {
936 self.block_export = Some(handle);
937 self
938 }
939
940 pub fn with_outbound_cross_chain_sender(mut self, sender: OutboundCrossChainSender) -> Self {
945 self.outbound_cross_chain_sender = Some(sender);
946 self
947 }
948
949 #[instrument(level = "trace", skip(self, certificate, notifier))]
950 #[inline]
951 pub async fn fully_handle_certificate_with_notifications<C>(
954 &self,
955 certificate: C,
956 notifier: &impl Notifier,
957 ) -> Result<ChainInfoResponse, WorkerError>
958 where
959 C: Certified + Clone + Send + 'static,
960 C::Value: ProcessableCertificate<Certificate = C>,
961 {
962 let notifications = (*notifier).clone();
963 let this = self.clone();
964 linera_base::Task::spawn(async move {
965 let (response, actions) =
966 <C::Value as ProcessableCertificate>::process_certificate(&this, certificate)
967 .await?;
968 notifications.notify(&actions.notifications);
969 let mut requests = VecDeque::from(actions.cross_chain_requests);
970 while let Some(request) = requests.pop_front() {
971 let actions = this.handle_cross_chain_request(request).await?;
972 requests.extend(actions.cross_chain_requests);
973 notifications.notify(&actions.notifications);
974 }
975 Ok(response)
976 })
977 .await
978 }
979
980 #[instrument(level = "trace", skip(self, certificate, notifier))]
984 #[inline]
985 pub async fn fully_handle_confirmed_certificate_with_notifications(
986 &self,
987 certificate: ConfirmedBlockCertificate,
988 mode: ProcessConfirmedBlockMode,
989 notifier: &impl Notifier,
990 ) -> Result<ChainInfoResponse, WorkerError> {
991 let notifications = (*notifier).clone();
992 let this = self.clone();
993 linera_base::Task::spawn(async move {
994 let (response, actions) =
995 Box::pin(this.handle_confirmed_certificate(certificate, mode, None)).await?;
996 notifications.notify(&actions.notifications);
997 let mut requests = VecDeque::from(actions.cross_chain_requests);
998 while let Some(request) = requests.pop_front() {
999 let actions = this.handle_cross_chain_request(request).await?;
1000 requests.extend(actions.cross_chain_requests);
1001 notifications.notify(&actions.notifications);
1002 }
1003 Ok(response)
1004 })
1005 .await
1006 }
1007
1008 async fn chain_read<R, F, Fut>(&self, chain_id: ChainId, f: F) -> Result<R, WorkerError>
1013 where
1014 F: FnOnce(OwnedRwLockReadGuard<ChainWorkerState<StorageClient>>) -> Fut,
1015 Fut: std::future::Future<Output = Result<R, WorkerError>>,
1016 {
1017 let state = self.get_or_create_chain_worker(chain_id).await?;
1018 let state_ref = &state;
1019 let result = Box::pin(wrap_future(async move {
1020 let guard = handle::read_lock(state_ref).await?;
1021 f(guard).await
1022 }))
1023 .await;
1024 if let Err(error) = &result {
1025 if error.must_reload_view() {
1026 self.evict_poisoned_worker(chain_id, &state);
1027 }
1028 }
1029 result
1030 }
1031
1032 async fn chain_write<R, F, Fut>(&self, chain_id: ChainId, f: F) -> Result<R, WorkerError>
1050 where
1051 F: FnOnce(handle::RollbackGuard<StorageClient>) -> Fut
1052 + linera_base::task::MaybeSend
1053 + 'static,
1054 Fut: std::future::Future<Output = Result<R, WorkerError>> + linera_base::task::MaybeSend,
1055 R: linera_base::task::MaybeSend + 'static,
1056 {
1057 let state = self.get_or_create_chain_worker(chain_id).await?;
1058 let this = self.clone();
1059 Box::pin(wrap_future(linera_base::task::run_detached(async move {
1060 let result = async {
1061 let guard = handle::write_lock(&state).await?;
1062 f(guard).await
1063 }
1064 .await;
1065 if let Err(error) = &result {
1066 if error.must_reload_view() {
1067 this.evict_poisoned_worker(chain_id, &state);
1068 } else if error.indicates_corrupted_chain_state() {
1069 this.spawn_reset_corrupted_chain_state(chain_id, state);
1070 }
1071 }
1072 result
1073 })))
1074 .await
1075 }
1076
1077 fn spawn_reset_corrupted_chain_state(
1088 &self,
1089 chain_id: ChainId,
1090 state: ChainWorkerArc<StorageClient>,
1091 ) where
1092 StorageClient: Clone,
1093 {
1094 let this = self.clone();
1095 linera_base::Task::spawn(async move {
1096 let requests = {
1097 let mut guard = match handle::write_lock(&state).await {
1098 Ok(guard) => guard,
1099 Err(error) => {
1100 tracing::error!(
1101 %chain_id, %error,
1102 "Failed to acquire write lock to reset corrupted chain state"
1103 );
1104 return;
1105 }
1106 };
1107 match guard.maybe_reset_corrupted_chain_state().await {
1108 Ok(Some(requests)) => requests,
1109 Ok(None) => return,
1110 Err(error) => {
1111 tracing::error!(
1112 %chain_id, %error, "Failed to reset corrupted chain state"
1113 );
1114 return;
1115 }
1116 }
1117 };
1118 if let Some(sender) = &this.outbound_cross_chain_sender {
1119 for request in requests {
1122 sender(request);
1123 }
1124 } else {
1125 let mut queue = VecDeque::from(requests);
1129 while let Some(request) = queue.pop_front() {
1130 match this.handle_cross_chain_request(request).await {
1131 Ok(actions) => queue.extend(actions.cross_chain_requests),
1132 Err(error) => {
1133 warn!(
1134 %chain_id, %error,
1135 "Failed to dispatch cross-chain request after \
1136 resetting corrupted chain state"
1137 );
1138 }
1139 }
1140 }
1141 }
1142 })
1143 .forget();
1144 }
1145
1146 #[cfg(with_testing)]
1149 pub fn resident_chain_worker_count(&self) -> usize {
1150 self.chain_workers
1151 .pin()
1152 .iter()
1153 .filter(
1154 |(_, future)| matches!(future.peek(), Some(Ok(weak)) if weak.strong_count() > 0),
1155 )
1156 .count()
1157 }
1158
1159 fn evict_poisoned_worker(&self, chain_id: ChainId, poisoned: &ChainWorkerArc<StorageClient>) {
1163 tracing::warn!(%chain_id, "Evicting poisoned chain worker from cache");
1164 let pin = self.chain_workers.pin();
1165 let weak_poisoned = Arc::downgrade(poisoned);
1166 let removed = pin.remove_if(&chain_id, |_key, future| {
1167 future
1168 .peek()
1169 .and_then(|r| r.clone().ok())
1170 .is_some_and(|weak| weak.ptr_eq(&weak_poisoned))
1171 });
1172 if removed.is_err() {
1173 tracing::trace!(%chain_id, "Poisoned worker entry already replaced; skipping eviction");
1174 }
1175 }
1176
1177 async fn get_or_create_chain_batch(
1179 &self,
1180 chain_id: ChainId,
1181 ) -> Result<(mpsc::UnboundedSender<BatchRequest>, Shared<BatchFuture>), WorkerError> {
1182 if let Some(batch_processor) = self.chain_batches.pin().get(&chain_id) {
1185 if let Some(future) = batch_processor.future.upgrade() {
1186 return Ok((batch_processor.sender.clone(), future));
1187 }
1188 }
1189 let (new_request_processor, new_future) = ChainBatchRequestProcessor::create(
1190 self.clone(),
1191 chain_id,
1192 self.chain_worker_config.cross_chain_batch_size_limit,
1193 );
1194 match self
1195 .chain_batches
1196 .pin()
1197 .compute(chain_id, |existing| match existing {
1198 Some((_, batch_processor)) => {
1199 if let Some(future) = batch_processor.future.upgrade() {
1200 papaya::Operation::Abort((batch_processor.sender.clone(), future))
1201 } else {
1202 papaya::Operation::Insert(new_request_processor.clone())
1203 }
1204 }
1205 None => papaya::Operation::Insert(new_request_processor.clone()),
1206 }) {
1207 papaya::Compute::Aborted((sender, future)) => Ok((sender, future)),
1208 papaya::Compute::Inserted(_, batch_processor)
1209 | papaya::Compute::Updated {
1210 new: (_, batch_processor),
1211 ..
1212 } => Ok((batch_processor.sender.clone(), new_future)),
1213 papaya::Compute::Removed { .. } => unreachable!(),
1214 }
1215 }
1216
1217 fn get_or_create_chain_worker(
1228 &self,
1229 chain_id: ChainId,
1230 ) -> std::pin::Pin<
1231 Box<
1232 impl std::future::Future<Output = Result<ChainWorkerArc<StorageClient>, WorkerError>> + '_,
1233 >,
1234 > {
1235 Box::pin(wrap_future(async move {
1236 loop {
1237 let (sender, receiver) = oneshot::channel();
1240 let shared_receiver = receiver.shared();
1241
1242 let wait_or_sender = {
1245 let pin = self.chain_workers.pin();
1246 match pin.compute(chain_id, |existing| match existing {
1247 Some((_, entry)) => match entry.peek() {
1248 Some(Ok(weak)) => match weak.upgrade() {
1249 Some(arc) => papaya::Operation::Abort(Ok(arc)),
1250 None => papaya::Operation::Insert(shared_receiver.clone()),
1251 },
1252 Some(Err(_)) => papaya::Operation::Insert(shared_receiver.clone()),
1253 None => papaya::Operation::Abort(Err(entry.clone())),
1254 },
1255 None => papaya::Operation::Insert(shared_receiver.clone()),
1256 }) {
1257 papaya::Compute::Aborted(Ok(arc), ..) => return Ok(arc),
1258 papaya::Compute::Aborted(Err(wait), ..) => Either::Left(wait),
1259 papaya::Compute::Inserted { .. } | papaya::Compute::Updated { .. } => {
1260 Either::Right(sender)
1261 }
1262 papaya::Compute::Removed { .. } => unreachable!(),
1263 }
1264 };
1265
1266 match wait_or_sender {
1267 Either::Left(wait) => {
1268 if let Ok(weak) = wait.await {
1270 if let Some(arc) = weak.upgrade() {
1271 return Ok(arc);
1272 }
1273 }
1274 }
1276 Either::Right(sender) => {
1277 let worker = self.load_chain_worker(chain_id).await?;
1281 if sender.send(Arc::downgrade(&worker)).is_err() {
1282 tracing::error!(%chain_id, "Receiver dropped while loading worker state.");
1283 continue;
1284 }
1285 return Ok(worker);
1286 }
1287 }
1288 }
1289 }))
1290 }
1291
1292 async fn load_chain_worker(
1294 &self,
1295 chain_id: ChainId,
1296 ) -> Result<ChainWorkerArc<StorageClient>, WorkerError> {
1297 let delivery_notifier = self
1298 .delivery_notifiers
1299 .lock()
1300 .unwrap()
1301 .entry(chain_id)
1302 .or_default()
1303 .clone();
1304
1305 let is_tracked = self.chain_modes.as_ref().is_none_or(|chain_modes| {
1309 chain_modes
1310 .read()
1311 .unwrap()
1312 .get(&chain_id)
1313 .is_some_and(ListeningMode::is_full)
1314 });
1315
1316 let (service_runtime_endpoint, service_runtime_task) =
1317 if self.chain_worker_config.long_lived_services {
1318 let actor =
1319 handle::ServiceRuntimeActor::spawn(chain_id, self.storage.thread_pool()).await;
1320 (Some(actor.endpoint), Some(actor.task))
1321 } else {
1322 (None, None)
1323 };
1324
1325 let state = crate::chain_worker::state::ChainWorkerState::load(
1326 self.chain_worker_config.clone(),
1327 self.storage.clone(),
1328 self.block_cache.clone(),
1329 self.execution_state_cache.clone(),
1330 self.chain_modes.clone(),
1331 delivery_notifier,
1332 chain_id,
1333 service_runtime_endpoint,
1334 service_runtime_task,
1335 self.block_export.clone(),
1336 )
1337 .await?;
1338
1339 Ok(handle::create_chain_worker(
1340 state,
1341 is_tracked,
1342 &self.chain_worker_config,
1343 ))
1344 }
1345
1346 #[instrument(level = "trace", skip(self, block))]
1351 pub async fn stage_block_execution(
1352 &self,
1353 block: ProposedBlock,
1354 round: Option<u32>,
1355 published_blobs: Vec<Blob>,
1356 policy: BundleExecutionPolicy,
1357 ) -> Result<
1358 (
1359 ProposedBlock,
1360 Block,
1361 ChainInfoResponse,
1362 ResourceTracker,
1363 HashSet<ChainId>,
1364 ),
1365 WorkerError,
1366 > {
1367 let chain_id = block.chain_id;
1368 self.chain_write(chain_id, move |mut guard| async move {
1369 guard
1370 .stage_block_execution(block, round, &published_blobs, policy)
1371 .await
1372 })
1373 .await
1374 }
1375
1376 #[instrument(level = "trace", skip(self, chain_id, query))]
1381 pub async fn query_application(
1382 &self,
1383 chain_id: ChainId,
1384 query: Query,
1385 block_hash: Option<CryptoHash>,
1386 ) -> Result<(QueryOutcome, BlockHeight), WorkerError> {
1387 self.chain_write(chain_id, move |mut guard| async move {
1388 guard.query_application(query, block_hash).await
1389 })
1390 .await
1391 }
1392
1393 #[instrument(level = "trace", skip(self, chain_id, application_id), fields(
1394 nickname = %self.nickname(),
1395 chain_id = %chain_id,
1396 application_id = %application_id
1397 ))]
1398 pub async fn describe_application(
1400 &self,
1401 chain_id: ChainId,
1402 application_id: ApplicationId,
1403 ) -> Result<ApplicationDescription, WorkerError> {
1404 let state = self.get_or_create_chain_worker(chain_id).await?;
1405 let guard = handle::read_lock_initialized(&state).await?;
1406 guard.describe_application_readonly(application_id).await
1407 }
1408
1409 #[instrument(
1411 level = "trace",
1412 skip(self, certificate, notify_when_messages_are_delivered),
1413 fields(
1414 nickname = %self.nickname(),
1415 chain_id = %certificate.block().header.chain_id,
1416 block_height = %certificate.block().header.height
1417 )
1418 )]
1419 async fn process_confirmed_block(
1420 &self,
1421 certificate: ConfirmedBlockCertificate,
1422 mode: ProcessConfirmedBlockMode,
1423 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1424 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
1425 let chain_id = certificate.block().header.chain_id;
1426 self.chain_write(chain_id, move |mut guard| async move {
1427 guard
1428 .process_confirmed_block(certificate, mode, notify_when_messages_are_delivered)
1429 .await
1430 })
1431 .await
1432 }
1433
1434 #[instrument(level = "trace", skip(self, certificate), fields(
1436 nickname = %self.nickname(),
1437 chain_id = %certificate.block().header.chain_id,
1438 block_height = %certificate.block().header.height
1439 ))]
1440 async fn process_validated_block(
1441 &self,
1442 certificate: ValidatedBlockCertificate,
1443 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
1444 let chain_id = certificate.block().header.chain_id;
1445 self.chain_write(chain_id, move |mut guard| async move {
1446 guard.process_validated_block(certificate).await
1447 })
1448 .await
1449 }
1450
1451 #[instrument(level = "trace", skip(self, certificate), fields(
1453 nickname = %self.nickname(),
1454 chain_id = %certificate.value().chain_id(),
1455 height = %certificate.value().height()
1456 ))]
1457 async fn process_timeout(
1458 &self,
1459 certificate: TimeoutCertificate,
1460 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
1461 let chain_id = certificate.value().chain_id();
1462 self.chain_write(chain_id, move |mut guard| async move {
1463 guard.process_timeout(certificate).await
1464 })
1465 .await
1466 }
1467
1468 #[instrument(level = "trace", skip(self, origin, recipient, bundles), fields(
1471 nickname = %self.nickname(),
1472 origin = %origin,
1473 recipient = %recipient,
1474 num_bundles = %bundles.len()
1475 ))]
1476 async fn process_cross_chain_update(
1477 &self,
1478 origin: ChainId,
1479 recipient: ChainId,
1480 bundles: Vec<(Epoch, MessageBundle)>,
1481 previous_height: Option<BlockHeight>,
1482 ) -> Result<CrossChainUpdateResult, WorkerError> {
1483 let (result_sender, receiver) = oneshot::channel();
1484 let request = BatchRequest::Update {
1485 origin,
1486 bundles,
1487 previous_height,
1488 result_sender,
1489 };
1490 self.enqueue_and_drive(recipient, request, receiver).await
1491 }
1492
1493 async fn confirm_updated_recipient(
1496 &self,
1497 sender: ChainId,
1498 recipient: ChainId,
1499 latest_height: BlockHeight,
1500 ) -> Result<NetworkActions, WorkerError> {
1501 let (result_sender, receiver) = oneshot::channel();
1502 let request = BatchRequest::Confirm {
1503 recipient,
1504 latest_height,
1505 result_sender,
1506 };
1507 self.enqueue_and_drive(sender, request, receiver).await
1508 }
1509
1510 async fn enqueue_and_drive<R>(
1513 &self,
1514 chain_id: ChainId,
1515 request: BatchRequest,
1516 mut receiver: oneshot::Receiver<Result<R, WorkerError>>,
1517 ) -> Result<R, WorkerError> {
1518 let mut pending = Some(request);
1519 loop {
1520 let (sender, future) = self.get_or_create_chain_batch(chain_id).await?;
1521 if let Some(request) = pending.take() {
1522 if let Err(mpsc::error::SendError(request)) = sender.send(request) {
1523 pending = Some(request);
1524 continue; }
1526 }
1527 match future::select(pin::pin!(&mut receiver), future).await {
1530 Either::Left((result, _)) => {
1531 return result.unwrap_or(Err(WorkerError::PoisonedWorker));
1537 }
1538 Either::Right(((), _)) => match receiver.try_recv() {
1539 Ok(result) => return result,
1540 Err(oneshot::error::TryRecvError::Empty) => {}
1541 Err(oneshot::error::TryRecvError::Closed) => {
1542 return Err(WorkerError::ChainError(Box::new(
1543 ChainError::InternalError("batch driver stopped".into()),
1544 )));
1545 }
1546 },
1547 }
1548 }
1549 }
1550
1551 #[instrument(level = "trace", skip(self, chain_id, height), fields(
1553 nickname = %self.nickname(),
1554 chain_id = %chain_id,
1555 height = %height
1556 ))]
1557 #[cfg(with_testing)]
1558 pub async fn read_certificate(
1559 &self,
1560 chain_id: ChainId,
1561 height: BlockHeight,
1562 ) -> Result<Option<CacheArc<ConfirmedBlockCertificate>>, WorkerError> {
1563 let state = self.get_or_create_chain_worker(chain_id).await?;
1564 let guard = handle::read_lock_initialized(&state).await?;
1565 guard.read_certificate(height).await
1566 }
1567
1568 #[cfg(with_testing)]
1571 pub async fn select_message_bundles(
1572 &self,
1573 recipient: ChainId,
1574 origin: &ChainId,
1575 next_height_to_receive: BlockHeight,
1576 last_anticipated_block_height: Option<BlockHeight>,
1577 bundles: Vec<(Epoch, MessageBundle)>,
1578 ) -> Result<Vec<MessageBundle>, WorkerError> {
1579 let state = self.get_or_create_chain_worker(recipient).await?;
1580 let guard = handle::read_lock(&state).await?;
1581 guard
1582 .select_message_bundles(
1583 origin,
1584 next_height_to_receive,
1585 last_anticipated_block_height,
1586 bundles,
1587 )
1588 .await
1589 }
1590
1591 #[cfg(with_testing)]
1594 pub async fn reset_and_reexecute_chain(
1595 &self,
1596 chain_id: ChainId,
1597 ) -> Result<Vec<CrossChainRequest>, WorkerError> {
1598 let state = self.get_or_create_chain_worker(chain_id).await?;
1599 let mut guard = handle::write_lock(&state).await?;
1600 guard.reset_and_reexecute_chain().await
1601 }
1602
1603 #[instrument(level = "trace", skip(self), fields(
1609 nickname = %self.nickname(),
1610 chain_id = %chain_id
1611 ))]
1612 pub async fn chain_state_view(
1613 &self,
1614 chain_id: ChainId,
1615 ) -> Result<ChainStateViewReadGuard<StorageClient>, WorkerError> {
1616 let state = self.get_or_create_chain_worker(chain_id).await?;
1617 let guard = handle::read_lock(&state).await?;
1618 Ok(ChainStateViewReadGuard(OwnedRwLockReadGuard::map(
1619 guard,
1620 |s| s.chain(),
1621 )))
1622 }
1623
1624 #[instrument(skip_all, fields(
1625 nick = self.nickname(),
1626 chain_id = format!("{:.8}", proposal.content.block.chain_id),
1627 height = %proposal.content.block.height,
1628 ))]
1629 pub async fn handle_block_proposal(
1631 &self,
1632 proposal: BlockProposal,
1633 ) -> (Result<ChainInfoResponse, WorkerError>, NetworkActions) {
1634 trace!("{} <-- {:?}", self.nickname(), proposal);
1635 #[cfg(with_metrics)]
1636 let round = proposal.content.round;
1637
1638 let chain_id = proposal.content.block.chain_id;
1639 let now = self.storage.clock().current_time();
1641 let block_timestamp = proposal.content.block.timestamp;
1642 let delta = block_timestamp.delta_since(now);
1643 let grace_period = TimeDelta::from_micros(
1644 u64::try_from(self.chain_worker_config.block_time_grace_period.as_micros())
1645 .unwrap_or(u64::MAX),
1646 );
1647 if delta > TimeDelta::ZERO && delta <= grace_period {
1648 self.storage.clock().sleep_until(block_timestamp).await;
1649 }
1650
1651 let outcome = self
1652 .chain_write(chain_id, move |mut guard| async move {
1653 Ok::<_, WorkerError>(guard.handle_block_proposal(proposal).await)
1654 })
1655 .await;
1656 let (result, actions) = match outcome {
1657 Ok((result, actions)) => (result, actions),
1658 Err(err) => (Err(err), NetworkActions::default()),
1659 };
1660 #[cfg(with_metrics)]
1661 if result.is_ok() {
1662 metrics::NUM_ROUNDS_IN_BLOCK_PROPOSAL
1663 .with_label_values(&[round.type_name()])
1664 .observe(round.number() as f64);
1665 }
1666 (result, actions)
1667 }
1668
1669 #[instrument(skip_all, fields(
1672 chain_id = %certificate.value.chain_id,
1673 hash = %certificate.value.value_hash,
1674 ))]
1675 pub async fn handle_lite_certificate(
1676 &self,
1677 certificate: LiteCertificate<'_>,
1678 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1679 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
1680 match self.full_certificate(certificate).await? {
1681 Either::Left(confirmed) => {
1682 Box::pin(self.handle_confirmed_certificate(
1683 confirmed,
1684 ProcessConfirmedBlockMode::Auto,
1685 notify_when_messages_are_delivered,
1686 ))
1687 .await
1688 }
1689 Either::Right(validated) => {
1690 if let Some(notifier) = notify_when_messages_are_delivered {
1691 if let Err(()) = notifier.send(()) {
1693 debug!("Failed to notify message delivery to caller (validation cert)");
1694 }
1695 }
1696 Box::pin(self.handle_validated_certificate(validated)).await
1697 }
1698 }
1699 }
1700
1701 #[instrument(skip_all, fields(
1703 nick = self.nickname(),
1704 chain_id = format!("{:.8}", certificate.block().header.chain_id),
1705 height = %certificate.block().header.height,
1706 ))]
1707 pub async fn handle_confirmed_certificate(
1708 &self,
1709 certificate: ConfirmedBlockCertificate,
1710 mode: ProcessConfirmedBlockMode,
1711 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1712 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
1713 trace!("{} <-- {:?}", self.nickname(), certificate);
1714 #[cfg(with_metrics)]
1715 let metrics_data = metrics::MetricsData::new(&certificate);
1716
1717 #[allow(unused_variables)]
1718 let (info, actions, outcome) = Box::pin(self.process_confirmed_block(
1719 certificate,
1720 mode,
1721 notify_when_messages_are_delivered,
1722 ))
1723 .await?;
1724
1725 #[cfg(with_metrics)]
1726 if matches!(outcome, BlockOutcome::Processed) {
1727 metrics_data.record();
1728 }
1729 Ok((info, actions))
1730 }
1731
1732 #[instrument(skip_all, fields(
1734 nick = self.nickname(),
1735 chain_id = format!("{:.8}", certificate.block().header.chain_id),
1736 height = %certificate.block().header.height,
1737 ))]
1738 pub async fn handle_validated_certificate(
1739 &self,
1740 certificate: ValidatedBlockCertificate,
1741 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
1742 trace!("{} <-- {:?}", self.nickname(), certificate);
1743
1744 #[cfg(with_metrics)]
1745 let round = certificate.round;
1746 #[cfg(with_metrics)]
1747 let cert_str = certificate.inner().to_log_str();
1748
1749 #[allow(unused_variables)]
1750 let (info, actions, outcome) = Box::pin(self.process_validated_block(certificate)).await?;
1751 #[cfg(with_metrics)]
1752 {
1753 if matches!(outcome, BlockOutcome::Processed) {
1754 metrics::NUM_ROUNDS_IN_CERTIFICATE
1755 .with_label_values(&[cert_str, round.type_name()])
1756 .observe(round.number() as f64);
1757 }
1758 }
1759 Ok((info, actions))
1760 }
1761
1762 #[instrument(skip_all, fields(
1764 nick = self.nickname(),
1765 chain_id = format!("{:.8}", certificate.inner().chain_id()),
1766 height = %certificate.inner().height(),
1767 ))]
1768 pub async fn handle_timeout_certificate(
1769 &self,
1770 certificate: TimeoutCertificate,
1771 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
1772 trace!("{} <-- {:?}", self.nickname(), certificate);
1773 self.process_timeout(certificate).await
1774 }
1775
1776 #[instrument(skip_all, fields(
1777 nick = self.nickname(),
1778 chain_id = format!("{:.8}", query.chain_id)
1779 ))]
1780 pub async fn handle_chain_info_query(
1782 &self,
1783 query: ChainInfoQuery,
1784 ) -> Result<ChainInfoResponse, WorkerError> {
1785 trace!("{} <-- {:?}", self.nickname(), query);
1786 #[cfg(with_metrics)]
1787 metrics::CHAIN_INFO_QUERIES.inc();
1788 let chain_id = query.chain_id;
1789 let result = self
1790 .chain_write(chain_id, move |mut guard| async move {
1791 guard.handle_chain_info_query(query).await
1792 })
1793 .await;
1794 trace!("{} --> {:?}", self.nickname(), result);
1795 result
1796 }
1797
1798 #[instrument(skip_all, fields(
1799 nick = self.nickname(),
1800 chain_id = format!("{:.8}", chain_id)
1801 ))]
1802 pub async fn download_pending_blob(
1804 &self,
1805 chain_id: ChainId,
1806 blob_id: BlobId,
1807 ) -> Result<CacheArc<Blob>, WorkerError> {
1808 trace!("{} <-- download_pending_blob({blob_id:8})", self.nickname());
1809 let result = self
1810 .chain_read(chain_id, |guard| async move {
1811 guard.download_pending_blob(blob_id).await
1812 })
1813 .await;
1814 trace!(
1815 "{} --> {:?}",
1816 self.nickname(),
1817 result.as_ref().map(|_| blob_id)
1818 );
1819 result
1820 }
1821
1822 #[instrument(skip_all, fields(
1823 nick = self.nickname(),
1824 chain_id = format!("{:.8}", chain_id)
1825 ))]
1826 pub async fn handle_pending_blob(
1828 &self,
1829 chain_id: ChainId,
1830 blob: Blob,
1831 ) -> Result<ChainInfoResponse, WorkerError> {
1832 let blob_id = blob.id();
1833 trace!("{} <-- handle_pending_blob({blob_id:8})", self.nickname());
1834 let result = self
1835 .chain_write(chain_id, move |mut guard| async move {
1836 guard.handle_pending_blob(blob).await
1837 })
1838 .await;
1839 trace!(
1840 "{} --> {:?}",
1841 self.nickname(),
1842 result.as_ref().map(|_| blob_id)
1843 );
1844 result
1845 }
1846
1847 #[instrument(skip_all, fields(
1848 nick = self.nickname(),
1849 chain_id = format!("{:.8}", request.target_chain_id())
1850 ))]
1851 pub async fn handle_cross_chain_request(
1853 &self,
1854 request: CrossChainRequest,
1855 ) -> Result<NetworkActions, WorkerError> {
1856 trace!("{} <-- {:?}", self.nickname(), request);
1857 match request {
1858 CrossChainRequest::UpdateRecipient {
1859 sender,
1860 recipient,
1861 bundles,
1862 previous_height,
1863 } => {
1864 let mut actions = NetworkActions::default();
1865 let origin = sender;
1866 match self
1867 .process_cross_chain_update(origin, recipient, bundles, previous_height)
1868 .await?
1869 {
1870 CrossChainUpdateResult::NothingToDo => {}
1871 CrossChainUpdateResult::Updated(height) => {
1872 actions.notifications.push(Notification {
1873 chain_id: recipient,
1874 reason: Reason::NewIncomingBundle { origin, height },
1875 });
1876 actions.cross_chain_requests.push(
1877 CrossChainRequest::ConfirmUpdatedRecipient {
1878 sender,
1879 recipient,
1880 latest_height: height,
1881 },
1882 );
1883 }
1884 CrossChainUpdateResult::GapDetected {
1885 origin,
1886 retransmit_from,
1887 } => {
1888 actions
1889 .cross_chain_requests
1890 .push(CrossChainRequest::RevertConfirm {
1891 sender: origin,
1892 recipient,
1893 retransmit_from,
1894 });
1895 }
1896 }
1897 Ok(actions)
1898 }
1899 CrossChainRequest::ConfirmUpdatedRecipient {
1900 sender,
1901 recipient,
1902 latest_height,
1903 } => {
1904 let actions = self
1905 .confirm_updated_recipient(sender, recipient, latest_height)
1906 .await?;
1907 Ok(actions)
1908 }
1909 CrossChainRequest::RevertConfirm {
1910 sender,
1911 recipient,
1912 retransmit_from,
1913 } => {
1914 self.chain_write(sender, move |mut guard| async move {
1915 guard
1916 .handle_revert_confirm(recipient, retransmit_from)
1917 .await
1918 })
1919 .await
1920 }
1921 }
1922 }
1923
1924 #[instrument(skip_all, fields(
1926 nickname = %self.nickname(),
1927 chain_id = %chain_id,
1928 num_trackers = %new_trackers.len()
1929 ))]
1930 pub async fn update_received_certificate_trackers(
1931 &self,
1932 chain_id: ChainId,
1933 new_trackers: BTreeMap<ValidatorPublicKey, u64>,
1934 ) -> Result<(), WorkerError> {
1935 self.chain_write(chain_id, move |mut guard| async move {
1936 guard
1937 .update_received_certificate_trackers(new_trackers)
1938 .await
1939 })
1940 .await
1941 }
1942
1943 #[instrument(skip_all, fields(
1945 nickname = %self.nickname(),
1946 chain_id = %chain_id,
1947 start = %start,
1948 end = %end
1949 ))]
1950 pub async fn get_preprocessed_block_hashes(
1951 &self,
1952 chain_id: ChainId,
1953 start: BlockHeight,
1954 end: BlockHeight,
1955 ) -> Result<Vec<CryptoHash>, WorkerError> {
1956 self.chain_read(chain_id, |guard| async move {
1957 guard.get_preprocessed_block_hashes(start, end).await
1958 })
1959 .await
1960 }
1961
1962 #[instrument(skip_all, fields(
1964 nickname = %self.nickname(),
1965 chain_id = %chain_id,
1966 origin = %origin
1967 ))]
1968 pub async fn get_inbox_next_height(
1969 &self,
1970 chain_id: ChainId,
1971 origin: ChainId,
1972 ) -> Result<BlockHeight, WorkerError> {
1973 self.chain_read(chain_id, |guard| async move {
1974 guard.get_inbox_next_height(origin).await
1975 })
1976 .await
1977 }
1978
1979 #[instrument(skip_all, fields(
1982 nickname = %self.nickname(),
1983 chain_id = %chain_id,
1984 num_blob_ids = %blob_ids.len()
1985 ))]
1986 pub async fn get_locking_blobs(
1987 &self,
1988 chain_id: ChainId,
1989 blob_ids: Vec<BlobId>,
1990 ) -> Result<Option<Vec<Blob>>, WorkerError> {
1991 self.chain_read(chain_id, |guard| async move {
1992 guard.get_locking_blobs(blob_ids).await
1993 })
1994 .await
1995 }
1996
1997 pub async fn get_block_hashes(
1999 &self,
2000 chain_id: ChainId,
2001 heights: Vec<BlockHeight>,
2002 ) -> Result<Vec<CryptoHash>, WorkerError> {
2003 self.chain_read(chain_id, |guard| async move {
2004 guard.get_block_hashes(heights).await
2005 })
2006 .await
2007 }
2008
2009 pub async fn get_proposed_blobs(
2011 &self,
2012 chain_id: ChainId,
2013 blob_ids: Vec<BlobId>,
2014 ) -> Result<Vec<Blob>, WorkerError> {
2015 self.chain_read(chain_id, |guard| async move {
2016 guard.get_proposed_blobs(blob_ids).await
2017 })
2018 .await
2019 }
2020
2021 pub async fn get_event_subscriptions(
2023 &self,
2024 chain_id: ChainId,
2025 ) -> Result<EventSubscriptionsResult, WorkerError> {
2026 self.chain_read(chain_id, |guard| async move {
2027 guard.get_event_subscriptions().await
2028 })
2029 .await
2030 }
2031
2032 pub async fn get_stream_indices(
2035 &self,
2036 chain_id: ChainId,
2037 stream_id: StreamId,
2038 ) -> Result<StreamCounts, WorkerError> {
2039 self.chain_read(chain_id, |guard| async move {
2040 guard.get_stream_indices(stream_id).await
2041 })
2042 .await
2043 }
2044
2045 pub async fn next_expected_events(
2047 &self,
2048 chain_id: ChainId,
2049 stream_ids: Vec<StreamId>,
2050 ) -> Result<BTreeMap<StreamId, u32>, WorkerError> {
2051 self.chain_read(chain_id, |guard| async move {
2052 guard.get_next_expected_events(stream_ids).await
2053 })
2054 .await
2055 }
2056
2057 pub async fn get_received_certificate_trackers(
2059 &self,
2060 chain_id: ChainId,
2061 ) -> Result<HashMap<ValidatorPublicKey, u64>, WorkerError> {
2062 self.chain_read(chain_id, |guard| async move {
2063 guard.get_received_certificate_trackers().await
2064 })
2065 .await
2066 }
2067
2068 pub async fn cross_chain_network_actions(
2072 &self,
2073 chain_id: ChainId,
2074 ) -> Result<NetworkActions, WorkerError> {
2075 if let Some(actions) = self
2079 .chain_read(chain_id, |guard| async move {
2080 guard.cross_chain_network_actions_if_reconciled().await
2081 })
2082 .await?
2083 {
2084 return Ok(actions);
2085 }
2086 self.chain_write(chain_id, |mut guard| async move {
2089 guard.reconcile_and_cross_chain_network_actions().await
2090 })
2091 .await
2092 }
2093
2094 pub async fn get_tip_state_and_outbox_info(
2096 &self,
2097 chain_id: ChainId,
2098 receiver_id: ChainId,
2099 ) -> Result<(BlockHeight, Option<BlockHeight>), WorkerError> {
2100 self.chain_read(chain_id, |guard| async move {
2101 guard.get_tip_state_and_outbox_info(receiver_id).await
2102 })
2103 .await
2104 }
2105
2106 pub async fn get_next_height_to_preprocess(
2108 &self,
2109 chain_id: ChainId,
2110 ) -> Result<BlockHeight, WorkerError> {
2111 self.chain_read(chain_id, |guard| async move {
2112 Ok(guard.get_next_height_to_preprocess())
2113 })
2114 .await
2115 }
2116}
2117
2118#[cfg(with_testing)]
2119impl<StorageClient> WorkerState<StorageClient>
2120where
2121 StorageClient: Storage + Clone + 'static,
2122{
2123 #[instrument(level = "trace", skip(self))]
2129 pub fn public_key(&self) -> ValidatorPublicKey {
2130 self.chain_worker_config
2131 .key_pair()
2132 .expect(
2133 "Test validator should have a key pair assigned to it \
2134 in order to obtain its public key",
2135 )
2136 .public()
2137 }
2138}