1use std::{
6 collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
7 future::Future,
8 pin,
9 sync::{Arc, Mutex, RwLock},
10 time::Duration,
11};
12
13use futures::{
14 future::{self, Either, Shared, WeakShared},
15 FutureExt as _,
16};
17use linera_base::{
18 crypto::{CryptoError, CryptoHash, ValidatorPublicKey},
19 data_types::{
20 ApplicationDescription, ArithmeticError, Blob, BlockHeight, Epoch, Round, TimeDelta,
21 Timestamp,
22 },
23 doc_scalar,
24 identifiers::{AccountOwner, ApplicationId, BlobId, ChainId, EventId, StreamId},
25};
26use linera_cache::{Arc as CacheArc, UniqueValueCache, ValueCache, DEFAULT_CLEANUP_INTERVAL_SECS};
27#[cfg(with_testing)]
28use linera_chain::ChainExecutionContext;
29use linera_chain::{
30 data_types::{BlockProposal, BundleExecutionPolicy, MessageBundle, ProposedBlock},
31 types::{
32 Block, CertificateValue, Certified, ConfirmedBlock, ConfirmedBlockCertificate,
33 GenericCertificate, LiteCertificate, Timeout, TimeoutCertificate, ValidatedBlock,
34 ValidatedBlockCertificate,
35 },
36 ChainError, ChainStateView, StreamCounts,
37};
38use linera_execution::{ExecutionError, ExecutionStateView, Query, QueryOutcome, ResourceTracker};
39use linera_storage::{Clock as _, Storage};
40use linera_views::{context::InactiveContext, ViewError};
41use serde::{Deserialize, Serialize};
42use thiserror::Error;
43use tokio::sync::{mpsc, oneshot, OwnedRwLockReadGuard};
44use tracing::{debug, instrument, trace, warn};
45
46pub struct ChainStateViewReadGuard<S: Storage>(
53 OwnedRwLockReadGuard<ChainWorkerState<S>, ChainStateView<S::Context>>,
54);
55
56impl<S: Storage> std::ops::Deref for ChainStateViewReadGuard<S> {
57 type Target = ChainStateView<S::Context>;
58
59 fn deref(&self) -> &Self::Target {
60 &self.0
61 }
62}
63
64pub(crate) use crate::chain_worker::EventSubscriptionsResult;
66use crate::{
67 chain_worker::{
68 handle, state::ChainWorkerState, BlockOutcome, ChainWorkerConfig, CrossChainUpdateResult,
69 DeliveryNotifier, ProcessConfirmedBlockMode,
70 },
71 client::{ChainModes, ListeningMode},
72 data_types::{ChainInfoQuery, ChainInfoResponse, CrossChainRequest},
73 notifier::Notifier,
74};
75
76pub const DEFAULT_BLOCK_CACHE_SIZE: usize = 5_000;
78pub const DEFAULT_EXECUTION_STATE_CACHE_SIZE: usize = 10_000;
80
81#[cfg(test)]
82#[path = "unit_tests/worker_tests.rs"]
83mod worker_tests;
84
85#[cfg(all(test, feature = "rocksdb"))]
86#[path = "unit_tests/worker_backup_tests.rs"]
87mod worker_backup_tests;
88
89#[cfg(not(web))]
92pub(crate) fn wrap_future<F: std::future::Future>(f: F) -> sync_wrapper::SyncFuture<F> {
93 sync_wrapper::SyncFuture::new(f)
94}
95
96#[cfg(web)]
99pub(crate) fn wrap_future<F: std::future::Future>(f: F) -> F {
100 f
101}
102
103#[cfg(with_metrics)]
104mod metrics {
105 use std::sync::LazyLock;
106
107 use linera_base::prometheus_util::{
108 exponential_bucket_interval, register_histogram, register_histogram_vec,
109 register_int_counter, register_int_counter_vec,
110 };
111 use linera_chain::{data_types::MessageAction, types::ConfirmedBlockCertificate};
112 use prometheus::{Histogram, HistogramVec, IntCounter, IntCounterVec};
113
114 pub static NUM_ROUNDS_IN_CERTIFICATE: LazyLock<HistogramVec> = LazyLock::new(|| {
115 register_histogram_vec(
116 "num_rounds_in_certificate",
117 "Number of rounds in certificate",
118 &["certificate_value", "round_type"],
119 exponential_bucket_interval(0.1, 50.0),
120 )
121 });
122
123 pub static NUM_ROUNDS_IN_BLOCK_PROPOSAL: LazyLock<HistogramVec> = LazyLock::new(|| {
124 register_histogram_vec(
125 "num_rounds_in_block_proposal",
126 "Number of rounds in block proposal",
127 &["round_type"],
128 exponential_bucket_interval(0.1, 50.0),
129 )
130 });
131
132 pub static TRANSACTION_COUNT: LazyLock<IntCounterVec> =
133 LazyLock::new(|| register_int_counter_vec("transaction_count", "Transaction count", &[]));
134
135 pub static INCOMING_BUNDLE_COUNT: LazyLock<IntCounter> =
136 LazyLock::new(|| register_int_counter("incoming_bundle_count", "Incoming bundle count"));
137
138 pub static REJECTED_BUNDLE_COUNT: LazyLock<IntCounter> =
139 LazyLock::new(|| register_int_counter("rejected_bundle_count", "Rejected bundle count"));
140
141 pub static INCOMING_MESSAGE_COUNT: LazyLock<IntCounter> =
142 LazyLock::new(|| register_int_counter("incoming_message_count", "Incoming message count"));
143
144 pub static OPERATION_COUNT: LazyLock<IntCounter> =
145 LazyLock::new(|| register_int_counter("operation_count", "Operation count"));
146
147 pub static OPERATIONS_PER_BLOCK: LazyLock<Histogram> = LazyLock::new(|| {
148 register_histogram(
149 "operations_per_block",
150 "Number of operations per block",
151 exponential_bucket_interval(1.0, 10000.0),
152 )
153 });
154
155 pub static INCOMING_BUNDLES_PER_BLOCK: LazyLock<Histogram> = LazyLock::new(|| {
156 register_histogram(
157 "incoming_bundles_per_block",
158 "Number of incoming bundles per block",
159 exponential_bucket_interval(1.0, 10000.0),
160 )
161 });
162
163 pub static TRANSACTIONS_PER_BLOCK: LazyLock<Histogram> = LazyLock::new(|| {
164 register_histogram(
165 "transactions_per_block",
166 "Number of transactions per block",
167 exponential_bucket_interval(1.0, 10000.0),
168 )
169 });
170
171 pub static NUM_BLOCKS: LazyLock<IntCounterVec> = LazyLock::new(|| {
172 register_int_counter_vec("num_blocks", "Number of blocks added to chains", &[])
173 });
174
175 pub static CERTIFICATES_SIGNED: LazyLock<IntCounterVec> = LazyLock::new(|| {
176 register_int_counter_vec(
177 "certificates_signed",
178 "Number of confirmed block certificates signed by each validator",
179 &["validator_name"],
180 )
181 });
182
183 pub static CHAIN_INFO_QUERIES: LazyLock<IntCounter> = LazyLock::new(|| {
184 register_int_counter(
185 "chain_info_queries",
186 "Number of chain info queries processed",
187 )
188 });
189
190 pub static CROSS_CHAIN_BATCH_SIZE: LazyLock<Histogram> = LazyLock::new(|| {
191 register_histogram(
192 "cross_chain_batch_size",
193 "Number of cross-chain requests coalesced into a single per-chain batch",
194 exponential_bucket_interval(1.0, 1000.0),
195 )
196 });
197
198 pub struct MetricsData {
200 certificate_log_str: &'static str,
201 round_type: &'static str,
202 round_number: u32,
203 confirmed_transactions: u64,
204 confirmed_incoming_bundles: u64,
205 confirmed_rejected_bundles: u64,
206 confirmed_incoming_messages: u64,
207 confirmed_operations: u64,
208 validators_with_signatures: Vec<String>,
209 }
210
211 impl MetricsData {
212 pub fn new(certificate: &ConfirmedBlockCertificate) -> Self {
214 Self {
215 certificate_log_str: certificate.inner().to_log_str(),
216 round_type: certificate.round.type_name(),
217 round_number: certificate.round.number(),
218 confirmed_transactions: certificate.block().body.transactions.len() as u64,
219 confirmed_incoming_bundles: certificate.block().body.incoming_bundles().count()
220 as u64,
221 confirmed_rejected_bundles: certificate
222 .block()
223 .body
224 .incoming_bundles()
225 .filter(|b| b.action == MessageAction::Reject)
226 .count() as u64,
227 confirmed_incoming_messages: certificate
228 .block()
229 .body
230 .incoming_bundles()
231 .map(|b| b.messages().count())
232 .sum::<usize>() as u64,
233 confirmed_operations: certificate.block().body.operations().count() as u64,
234 validators_with_signatures: certificate
235 .signatures()
236 .iter()
237 .map(|(validator_name, _)| validator_name.to_string())
238 .collect(),
239 }
240 }
241
242 pub fn record(self) {
244 NUM_BLOCKS.with_label_values(&[]).inc();
245 NUM_ROUNDS_IN_CERTIFICATE
246 .with_label_values(&[self.certificate_log_str, self.round_type])
247 .observe(self.round_number as f64);
248 TRANSACTIONS_PER_BLOCK.observe(self.confirmed_transactions as f64);
249 INCOMING_BUNDLES_PER_BLOCK.observe(self.confirmed_incoming_bundles as f64);
250 OPERATIONS_PER_BLOCK.observe(self.confirmed_operations as f64);
251 if self.confirmed_transactions > 0 {
252 TRANSACTION_COUNT
253 .with_label_values(&[])
254 .inc_by(self.confirmed_transactions);
255 if self.confirmed_incoming_bundles > 0 {
256 INCOMING_BUNDLE_COUNT.inc_by(self.confirmed_incoming_bundles);
257 }
258 if self.confirmed_rejected_bundles > 0 {
259 REJECTED_BUNDLE_COUNT.inc_by(self.confirmed_rejected_bundles);
260 }
261 if self.confirmed_incoming_messages > 0 {
262 INCOMING_MESSAGE_COUNT.inc_by(self.confirmed_incoming_messages);
263 }
264 if self.confirmed_operations > 0 {
265 OPERATION_COUNT.inc_by(self.confirmed_operations);
266 }
267 }
268
269 for validator_name in self.validators_with_signatures {
270 CERTIFICATES_SIGNED
271 .with_label_values(&[&validator_name])
272 .inc();
273 }
274 }
275 }
276}
277
278#[derive(Default, Debug)]
280pub struct NetworkActions {
281 pub cross_chain_requests: Vec<CrossChainRequest>,
283 pub notifications: Vec<Notification>,
285}
286
287impl NetworkActions {
288 pub fn extend(&mut self, other: NetworkActions) {
290 self.cross_chain_requests.extend(other.cross_chain_requests);
291 self.notifications.extend(other.notifications);
292 }
293}
294
295#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
296#[allow(missing_docs)]
298pub struct Notification {
299 pub chain_id: ChainId,
300 pub reason: Reason,
301}
302
303doc_scalar!(
304 Notification,
305 "Notify that a chain has a new certified block or a new message"
306);
307
308#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
309#[allow(missing_docs)]
311pub enum Reason {
312 NewBlock {
313 height: BlockHeight,
314 hash: CryptoHash,
315 },
316 NewEvents {
317 height: BlockHeight,
318 block_hash: CryptoHash,
319 event_streams: BTreeSet<StreamId>,
320 },
321 NewIncomingBundle {
322 origin: ChainId,
323 height: BlockHeight,
324 },
325 NewRound {
326 height: BlockHeight,
327 round: Round,
328 },
329 BlockExecuted {
330 height: BlockHeight,
331 hash: CryptoHash,
332 },
333}
334
335#[derive(Debug, Error, strum::IntoStaticStr)]
337#[allow(missing_docs)]
338pub enum WorkerError {
339 #[error(transparent)]
340 CryptoError(#[from] CryptoError),
341
342 #[error(transparent)]
343 ArithmeticError(#[from] ArithmeticError),
344
345 #[error(transparent)]
346 ViewError(#[from] ViewError),
347
348 #[error("Certificates referenced from chain state are missing in storage: {0:?}")]
349 ReadCertificatesError(Vec<CryptoHash>),
350
351 #[error(transparent)]
352 ChainError(#[from] Box<ChainError>),
353
354 #[error(transparent)]
355 BcsError(#[from] bcs::Error),
356
357 #[error("Block was not signed by an authorized owner")]
359 InvalidOwner,
360
361 #[error("Operations in the block are not authenticated by the proper owner: {0}")]
362 InvalidSigner(AccountOwner),
363
364 #[error(
366 "Chain is expecting a next block at height {expected_block_height} but the given block \
367 is at height {found_block_height} instead"
368 )]
369 UnexpectedBlockHeight {
370 expected_block_height: BlockHeight,
371 found_block_height: BlockHeight,
372 },
373 #[error("Unexpected epoch {epoch}: chain {chain_id} is at {chain_epoch}")]
374 InvalidEpoch {
375 chain_id: ChainId,
376 chain_epoch: Epoch,
377 epoch: Epoch,
378 },
379
380 #[error("Events not found: {0:?}")]
381 EventsNotFound(Vec<EventId>),
382
383 #[error("Invalid cross-chain request")]
385 InvalidCrossChainRequest,
386 #[error("The block does not contain the hash that we expected for the previous block")]
387 InvalidBlockChaining,
388 #[error(
389 "Block timestamp ({block_timestamp}) is further in the future from local time \
390 ({local_time}) than block time grace period ({block_time_grace_period:?})"
391 )]
392 InvalidTimestamp {
393 block_timestamp: Timestamp,
394 local_time: Timestamp,
395 block_time_grace_period: Duration,
396 },
397 #[error("We don't have the value for the certificate.")]
398 MissingCertificateValue,
399 #[error("The hash certificate doesn't match its value.")]
400 InvalidLiteCertificate,
401 #[error("Fast blocks cannot query oracles")]
402 FastBlockUsingOracles,
403 #[error("Blobs not found: {0:?}")]
404 BlobsNotFound(Vec<BlobId>),
405 #[error("Blocks not found: {0:?}")]
412 BlocksNotFound(Vec<CryptoHash>),
413 #[error("Block hash at height {height} for chain {chain_id} not found")]
414 BlockHashNotFound {
415 height: BlockHeight,
416 chain_id: ChainId,
417 },
418 #[error("Block at height {height} on chain {chain_id} not found in local storage")]
419 LocalBlockNotFound {
420 height: BlockHeight,
421 chain_id: ChainId,
422 },
423 #[error("The block proposal is invalid: {0}")]
424 InvalidBlockProposal(String),
425 #[error("Blob was not required by any pending block")]
426 UnexpectedBlob,
427 #[error("Number of published blobs per block must not exceed {0}")]
428 TooManyPublishedBlobs(u64),
429 #[error("Missing network description")]
430 MissingNetworkDescription,
431 #[error("thread error: {0}")]
432 Thread(#[from] web_thread_pool::Error),
433 #[error("Chain worker was poisoned by a journal resolution failure")]
434 PoisonedWorker,
435 #[error("Cross-chain batch was rolled back due to an error in another request")]
436 BatchRolledBack,
437}
438
439impl WorkerError {
440 pub fn is_local(&self) -> bool {
444 match self {
445 WorkerError::CryptoError(_)
446 | WorkerError::ArithmeticError(_)
447 | WorkerError::InvalidOwner
448 | WorkerError::InvalidSigner(_)
449 | WorkerError::UnexpectedBlockHeight { .. }
450 | WorkerError::InvalidEpoch { .. }
451 | WorkerError::EventsNotFound(_)
452 | WorkerError::InvalidBlockChaining
453 | WorkerError::InvalidTimestamp { .. }
454 | WorkerError::MissingCertificateValue
455 | WorkerError::InvalidLiteCertificate
456 | WorkerError::FastBlockUsingOracles
457 | WorkerError::BlobsNotFound(_)
458 | WorkerError::BlocksNotFound(_)
459 | WorkerError::InvalidBlockProposal(_)
460 | WorkerError::UnexpectedBlob
461 | WorkerError::TooManyPublishedBlobs(_)
462 | WorkerError::ViewError(ViewError::NotFound(_)) => false,
463 WorkerError::BcsError(_)
464 | WorkerError::InvalidCrossChainRequest
465 | WorkerError::ViewError(_)
466 | WorkerError::BlockHashNotFound { .. }
467 | WorkerError::LocalBlockNotFound { .. }
468 | WorkerError::MissingNetworkDescription
469 | WorkerError::Thread(_)
470 | WorkerError::ReadCertificatesError(_)
471 | WorkerError::PoisonedWorker
472 | WorkerError::BatchRolledBack => true,
473 WorkerError::ChainError(chain_error) => chain_error.is_local(),
474 }
475 }
476
477 pub fn error_type(&self) -> String {
483 match self {
484 WorkerError::ChainError(chain_error) => chain_error.error_type(),
485 other => {
486 let variant: &'static str = other.into();
487 format!("WorkerError::{variant}")
488 }
489 }
490 }
491
492 pub(crate) fn must_reload_view(&self) -> bool {
495 matches!(
496 self,
497 WorkerError::PoisonedWorker
498 | WorkerError::ViewError(ViewError::StoreError {
499 must_reload_view: true,
500 ..
501 })
502 )
503 }
504
505 pub(crate) fn indicates_corrupted_chain_state(&self) -> bool {
509 matches!(
510 self,
511 WorkerError::ChainError(chain_error)
512 if matches!(chain_error.as_ref(), ChainError::CorruptedChainState(_))
513 )
514 }
515}
516
517impl From<ChainError> for WorkerError {
518 #[instrument(level = "trace", skip(chain_error))]
519 fn from(chain_error: ChainError) -> Self {
520 match chain_error {
521 ChainError::ExecutionError(execution_error, context) => match *execution_error {
522 ExecutionError::BlobsNotFound(blob_ids) => Self::BlobsNotFound(blob_ids),
523 ExecutionError::EventsNotFound(event_ids) => Self::EventsNotFound(event_ids),
524 _ => Self::ChainError(Box::new(ChainError::ExecutionError(
525 execution_error,
526 context,
527 ))),
528 },
529 error => Self::ChainError(Box::new(error)),
530 }
531 }
532}
533
534#[cfg(with_testing)]
535impl WorkerError {
536 pub fn expect_execution_error(self, expected_context: ChainExecutionContext) -> ExecutionError {
542 let WorkerError::ChainError(chain_error) = self else {
543 panic!("Expected an `ExecutionError`. Got: {self:#?}");
544 };
545
546 let ChainError::ExecutionError(execution_error, context) = *chain_error else {
547 panic!("Expected an `ExecutionError`. Got: {chain_error:#?}");
548 };
549
550 assert_eq!(context, expected_context);
551
552 *execution_error
553 }
554}
555
556type ChainWorkerArc<S> = Arc<tokio::sync::RwLock<ChainWorkerState<S>>>;
557type ChainWorkerWeak<S> = std::sync::Weak<tokio::sync::RwLock<ChainWorkerState<S>>>;
558type ChainWorkerFuture<S> = Shared<oneshot::Receiver<ChainWorkerWeak<S>>>;
559
560type ChainWorkerMap<S> = Arc<papaya::HashMap<ChainId, ChainWorkerFuture<S>>>;
568
569pub(crate) enum BatchRequest {
571 Update {
572 origin: ChainId,
573 bundles: Vec<(Epoch, MessageBundle)>,
574 previous_height: Option<BlockHeight>,
575 result_sender: oneshot::Sender<Result<CrossChainUpdateResult, WorkerError>>,
576 },
577 Confirm {
578 recipient: ChainId,
579 latest_height: BlockHeight,
580 result_sender: oneshot::Sender<Result<NetworkActions, WorkerError>>,
581 },
582}
583
584#[cfg(not(web))]
592type BatchFuture = pin::Pin<Box<dyn Future<Output = ()> + Send>>;
593#[cfg(web)]
594type BatchFuture = pin::Pin<Box<dyn Future<Output = ()>>>;
595
596#[derive(Clone)]
597struct ChainBatchRequestProcessor {
598 sender: mpsc::UnboundedSender<BatchRequest>,
600 future: WeakShared<BatchFuture>,
603}
604
605impl ChainBatchRequestProcessor {
606 fn create<StorageClient>(
607 worker: WorkerState<StorageClient>,
608 chain_id: ChainId,
609 batch_size_limit: usize,
610 ) -> (ChainBatchRequestProcessor, Shared<BatchFuture>)
611 where
612 StorageClient: Storage + Clone + 'static,
613 {
614 let (sender, mut receiver) = mpsc::unbounded_channel();
615 let future: BatchFuture = Box::pin(async move {
616 while let Some(first) = receiver.recv().await {
617 let mut requests = vec![first];
618 while requests.len() < batch_size_limit {
619 match receiver.try_recv() {
620 Ok(request) => requests.push(request),
621 Err(_) => break,
622 }
623 }
624 #[cfg(with_metrics)]
625 metrics::CROSS_CHAIN_BATCH_SIZE.observe(requests.len() as f64);
626 if let Err(error) = worker
637 .chain_write(chain_id, move |mut guard| async move {
638 guard.process_batch(requests).await
639 })
640 .await
641 {
642 tracing::warn!(%chain_id, %error, "cross-chain batch could not be processed");
643 }
644 }
645 });
646 let shared = future.shared();
647 let weak = shared.downgrade().expect("future has not been polled yet");
648 let batch_processor = ChainBatchRequestProcessor {
649 sender,
650 future: weak,
651 };
652 (batch_processor, shared)
653 }
654}
655
656type ChainBatchMap = Arc<papaya::HashMap<ChainId, ChainBatchRequestProcessor>>;
657
658fn start_sweep<S: Storage + Clone + 'static>(
662 chain_workers: &ChainWorkerMap<S>,
663 config: &ChainWorkerConfig,
664) {
665 let interval = match (config.ttl, config.sender_chain_ttl) {
668 (None, None) => return,
669 (Some(d), None) | (None, Some(d)) => d,
670 (Some(a), Some(b)) => a.min(b),
671 };
672 let weak_map = Arc::downgrade(chain_workers);
673 linera_base::Task::spawn(async move {
674 loop {
675 linera_base::time::timer::sleep(interval).await;
676 let Some(map) = weak_map.upgrade() else {
677 break;
678 };
679 map.pin_owned().retain(|_, shared| match shared.peek() {
680 Some(Ok(weak)) => weak.strong_count() > 0,
681 Some(Err(_)) => false, None => true, });
684 }
685 })
686 .forget();
687}
688
689pub struct WorkerState<StorageClient: Storage> {
691 storage: StorageClient,
693 chain_worker_config: ChainWorkerConfig,
695 block_cache: Arc<ValueCache<CryptoHash, ConfirmedBlock>>,
696 execution_state_cache:
697 Option<Arc<UniqueValueCache<CryptoHash, ExecutionStateView<InactiveContext>>>>,
698 pub(crate) chain_modes: Option<Arc<RwLock<ChainModes>>>,
700 delivery_notifiers: Arc<Mutex<DeliveryNotifiers>>,
703 chain_workers: ChainWorkerMap<StorageClient>,
707 chain_batches: ChainBatchMap,
709 outbound_cross_chain_sender: Option<OutboundCrossChainSender>,
715}
716
717pub type OutboundCrossChainSender = Arc<dyn Fn(CrossChainRequest) + Send + Sync>;
720
721impl<StorageClient> Clone for WorkerState<StorageClient>
722where
723 StorageClient: Storage + Clone,
724{
725 fn clone(&self) -> Self {
726 WorkerState {
727 storage: self.storage.clone(),
728 chain_worker_config: self.chain_worker_config.clone(),
729 block_cache: self.block_cache.clone(),
730 execution_state_cache: self.execution_state_cache.clone(),
731 chain_modes: self.chain_modes.clone(),
732 delivery_notifiers: self.delivery_notifiers.clone(),
733 chain_workers: self.chain_workers.clone(),
734 chain_batches: self.chain_batches.clone(),
735 outbound_cross_chain_sender: self.outbound_cross_chain_sender.clone(),
736 }
737 }
738}
739
740pub(crate) type DeliveryNotifiers = HashMap<ChainId, DeliveryNotifier>;
741
742impl<StorageClient> WorkerState<StorageClient>
743where
744 StorageClient: Storage,
745{
746 #[cfg(with_testing)]
748 #[instrument(level = "trace", skip(self))]
749 pub fn with_cross_chain_message_chunk_limit(mut self, limit: usize) -> Self {
750 self.chain_worker_config.cross_chain_message_chunk_limit = limit;
751 self
752 }
753
754 #[cfg(with_testing)]
756 pub fn set_cross_chain_message_chunk_limit(&mut self, limit: usize) {
757 self.chain_worker_config.cross_chain_message_chunk_limit = limit;
758 }
759
760 #[cfg(with_testing)]
762 #[instrument(level = "trace", skip(self, value))]
763 pub fn with_allow_revert_confirm(mut self, value: bool) -> Self {
764 self.chain_worker_config.allow_revert_confirm = value;
765 self
766 }
767
768 #[instrument(level = "trace", skip(self))]
770 pub fn nickname(&self) -> &str {
771 &self.chain_worker_config.nickname
772 }
773
774 #[instrument(level = "trace", skip(self))]
776 #[cfg(not(feature = "test"))]
777 pub(crate) fn storage_client(&self) -> &StorageClient {
778 &self.storage
779 }
780
781 #[instrument(level = "trace", skip(self))]
784 #[cfg(feature = "test")]
785 pub fn storage_client(&self) -> &StorageClient {
786 &self.storage
787 }
788
789 #[instrument(level = "trace", skip(self, certificate))]
790 pub(crate) async fn full_certificate(
791 &self,
792 certificate: LiteCertificate<'_>,
793 ) -> Result<Either<ConfirmedBlockCertificate, ValidatedBlockCertificate>, WorkerError> {
794 let block = self
795 .block_cache
796 .get(&certificate.value.value_hash)
797 .ok_or(WorkerError::MissingCertificateValue)?;
798 let block = CacheArc::unwrap_or_clone(block);
799
800 match certificate.value.kind {
801 linera_chain::types::CertificateKind::Confirmed => Ok(Either::Left(
802 certificate
803 .into_confirmed_certificate(block)
804 .ok_or(WorkerError::InvalidLiteCertificate)?,
805 )),
806 linera_chain::types::CertificateKind::Validated => {
807 let value = ValidatedBlock::from_hashed(block.into_inner());
808 Ok(Either::Right(
809 certificate
810 .into_validated_certificate(value)
811 .ok_or(WorkerError::InvalidLiteCertificate)?,
812 ))
813 }
814 _ => Err(WorkerError::InvalidLiteCertificate),
815 }
816 }
817}
818
819#[allow(async_fn_in_trait)]
820#[cfg_attr(not(web), trait_variant::make(Send))]
821pub trait ProcessableCertificate: CertificateValue + Sized + 'static {
823 type Certificate: Certified<Value = Self> + Clone + Send + Sync + 'static;
826
827 fn make_certificate(
830 quorum: GenericCertificate<Self>,
831 justification: linera_chain::justification::JustificationChain,
832 ) -> Self::Certificate;
833
834 async fn process_certificate<S: Storage + Clone + 'static>(
836 worker: &WorkerState<S>,
837 certificate: Self::Certificate,
838 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError>;
839}
840
841impl ProcessableCertificate for ConfirmedBlock {
842 type Certificate = ConfirmedBlockCertificate;
843
844 fn make_certificate(
845 quorum: GenericCertificate<Self>,
846 justification: linera_chain::justification::JustificationChain,
847 ) -> Self::Certificate {
848 ConfirmedBlockCertificate::from_parts(quorum, justification)
849 }
850
851 async fn process_certificate<S: Storage + Clone + 'static>(
852 worker: &WorkerState<S>,
853 certificate: ConfirmedBlockCertificate,
854 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
855 Box::pin(worker.handle_confirmed_certificate(
856 certificate,
857 ProcessConfirmedBlockMode::Auto,
858 None,
859 ))
860 .await
861 }
862}
863
864impl ProcessableCertificate for ValidatedBlock {
865 type Certificate = ValidatedBlockCertificate;
866
867 fn make_certificate(
868 quorum: GenericCertificate<Self>,
869 justification: linera_chain::justification::JustificationChain,
870 ) -> Self::Certificate {
871 ValidatedBlockCertificate::from_parts(quorum, justification)
872 }
873
874 async fn process_certificate<S: Storage + Clone + 'static>(
875 worker: &WorkerState<S>,
876 certificate: ValidatedBlockCertificate,
877 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
878 Box::pin(worker.handle_validated_certificate(certificate)).await
879 }
880}
881
882impl ProcessableCertificate for Timeout {
883 type Certificate = TimeoutCertificate;
884
885 fn make_certificate(
886 quorum: GenericCertificate<Self>,
887 _justification: linera_chain::justification::JustificationChain,
888 ) -> Self::Certificate {
889 quorum
890 }
891
892 async fn process_certificate<S: Storage + Clone + 'static>(
893 worker: &WorkerState<S>,
894 certificate: TimeoutCertificate,
895 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
896 worker.handle_timeout_certificate(certificate).await
897 }
898}
899
900impl<StorageClient> WorkerState<StorageClient>
901where
902 StorageClient: Storage + Clone + 'static,
903{
904 #[instrument(level = "trace", skip(storage, chain_worker_config))]
909 pub fn new(
910 storage: StorageClient,
911 chain_worker_config: ChainWorkerConfig,
912 chain_modes: Option<Arc<RwLock<ChainModes>>>,
913 ) -> Self {
914 let chain_workers = Arc::new(papaya::HashMap::new());
915 start_sweep(&chain_workers, &chain_worker_config);
916 let block_cache_size = chain_worker_config.block_cache_size;
917 let execution_state_cache_size = chain_worker_config.execution_state_cache_size;
918 WorkerState {
919 storage,
920 chain_worker_config,
921 block_cache: Arc::new(ValueCache::new(
922 "worker_block",
923 block_cache_size,
924 DEFAULT_CLEANUP_INTERVAL_SECS,
925 )),
926 execution_state_cache: (execution_state_cache_size > 0)
927 .then(|| Arc::new(UniqueValueCache::new(execution_state_cache_size))),
928 chain_modes,
929 delivery_notifiers: Arc::default(),
930 chain_workers,
931 #[cfg_attr(web, expect(clippy::arc_with_non_send_sync))]
935 chain_batches: Arc::new(papaya::HashMap::new()),
936 outbound_cross_chain_sender: None,
937 }
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 fn evict_poisoned_worker(&self, chain_id: ChainId, poisoned: &ChainWorkerArc<StorageClient>) {
1150 tracing::warn!(%chain_id, "Evicting poisoned chain worker from cache");
1151 let pin = self.chain_workers.pin();
1152 let weak_poisoned = Arc::downgrade(poisoned);
1153 let removed = pin.remove_if(&chain_id, |_key, future| {
1154 future
1155 .peek()
1156 .and_then(|r| r.clone().ok())
1157 .is_some_and(|weak| weak.ptr_eq(&weak_poisoned))
1158 });
1159 if removed.is_err() {
1160 tracing::trace!(%chain_id, "Poisoned worker entry already replaced; skipping eviction");
1161 }
1162 }
1163
1164 async fn get_or_create_chain_batch(
1166 &self,
1167 chain_id: ChainId,
1168 ) -> Result<(mpsc::UnboundedSender<BatchRequest>, Shared<BatchFuture>), WorkerError> {
1169 if let Some(batch_processor) = self.chain_batches.pin().get(&chain_id) {
1172 if let Some(future) = batch_processor.future.upgrade() {
1173 return Ok((batch_processor.sender.clone(), future));
1174 }
1175 }
1176 let (new_request_processor, new_future) = ChainBatchRequestProcessor::create(
1177 self.clone(),
1178 chain_id,
1179 self.chain_worker_config.cross_chain_batch_size_limit,
1180 );
1181 match self
1182 .chain_batches
1183 .pin()
1184 .compute(chain_id, |existing| match existing {
1185 Some((_, batch_processor)) => {
1186 if let Some(future) = batch_processor.future.upgrade() {
1187 papaya::Operation::Abort((batch_processor.sender.clone(), future))
1188 } else {
1189 papaya::Operation::Insert(new_request_processor.clone())
1190 }
1191 }
1192 None => papaya::Operation::Insert(new_request_processor.clone()),
1193 }) {
1194 papaya::Compute::Aborted((sender, future)) => Ok((sender, future)),
1195 papaya::Compute::Inserted(_, batch_processor)
1196 | papaya::Compute::Updated {
1197 new: (_, batch_processor),
1198 ..
1199 } => Ok((batch_processor.sender.clone(), new_future)),
1200 papaya::Compute::Removed { .. } => unreachable!(),
1201 }
1202 }
1203
1204 fn get_or_create_chain_worker(
1215 &self,
1216 chain_id: ChainId,
1217 ) -> std::pin::Pin<
1218 Box<
1219 impl std::future::Future<Output = Result<ChainWorkerArc<StorageClient>, WorkerError>> + '_,
1220 >,
1221 > {
1222 Box::pin(wrap_future(async move {
1223 loop {
1224 let (sender, receiver) = oneshot::channel();
1227 let shared_receiver = receiver.shared();
1228
1229 let wait_or_sender = {
1232 let pin = self.chain_workers.pin();
1233 match pin.compute(chain_id, |existing| match existing {
1234 Some((_, entry)) => match entry.peek() {
1235 Some(Ok(weak)) => match weak.upgrade() {
1236 Some(arc) => papaya::Operation::Abort(Ok(arc)),
1237 None => papaya::Operation::Insert(shared_receiver.clone()),
1238 },
1239 Some(Err(_)) => papaya::Operation::Insert(shared_receiver.clone()),
1240 None => papaya::Operation::Abort(Err(entry.clone())),
1241 },
1242 None => papaya::Operation::Insert(shared_receiver.clone()),
1243 }) {
1244 papaya::Compute::Aborted(Ok(arc), ..) => return Ok(arc),
1245 papaya::Compute::Aborted(Err(wait), ..) => Either::Left(wait),
1246 papaya::Compute::Inserted { .. } | papaya::Compute::Updated { .. } => {
1247 Either::Right(sender)
1248 }
1249 papaya::Compute::Removed { .. } => unreachable!(),
1250 }
1251 };
1252
1253 match wait_or_sender {
1254 Either::Left(wait) => {
1255 if let Ok(weak) = wait.await {
1257 if let Some(arc) = weak.upgrade() {
1258 return Ok(arc);
1259 }
1260 }
1261 }
1263 Either::Right(sender) => {
1264 let worker = self.load_chain_worker(chain_id).await?;
1268 if sender.send(Arc::downgrade(&worker)).is_err() {
1269 tracing::error!(%chain_id, "Receiver dropped while loading worker state.");
1270 continue;
1271 }
1272 return Ok(worker);
1273 }
1274 }
1275 }
1276 }))
1277 }
1278
1279 async fn load_chain_worker(
1281 &self,
1282 chain_id: ChainId,
1283 ) -> Result<ChainWorkerArc<StorageClient>, WorkerError> {
1284 let delivery_notifier = self
1285 .delivery_notifiers
1286 .lock()
1287 .unwrap()
1288 .entry(chain_id)
1289 .or_default()
1290 .clone();
1291
1292 let is_tracked = self.chain_modes.as_ref().is_none_or(|chain_modes| {
1296 chain_modes
1297 .read()
1298 .unwrap()
1299 .get(&chain_id)
1300 .is_some_and(ListeningMode::is_full)
1301 });
1302
1303 let (service_runtime_endpoint, service_runtime_task) =
1304 if self.chain_worker_config.long_lived_services {
1305 let actor =
1306 handle::ServiceRuntimeActor::spawn(chain_id, self.storage.thread_pool()).await;
1307 (Some(actor.endpoint), Some(actor.task))
1308 } else {
1309 (None, None)
1310 };
1311
1312 let state = crate::chain_worker::state::ChainWorkerState::load(
1313 self.chain_worker_config.clone(),
1314 self.storage.clone(),
1315 self.block_cache.clone(),
1316 self.execution_state_cache.clone(),
1317 self.chain_modes.clone(),
1318 delivery_notifier,
1319 chain_id,
1320 service_runtime_endpoint,
1321 service_runtime_task,
1322 )
1323 .await?;
1324
1325 Ok(handle::create_chain_worker(
1326 state,
1327 is_tracked,
1328 &self.chain_worker_config,
1329 ))
1330 }
1331
1332 #[instrument(level = "trace", skip(self, block))]
1337 pub async fn stage_block_execution(
1338 &self,
1339 block: ProposedBlock,
1340 round: Option<u32>,
1341 published_blobs: Vec<Blob>,
1342 policy: BundleExecutionPolicy,
1343 ) -> Result<
1344 (
1345 ProposedBlock,
1346 Block,
1347 ChainInfoResponse,
1348 ResourceTracker,
1349 HashSet<ChainId>,
1350 ),
1351 WorkerError,
1352 > {
1353 let chain_id = block.chain_id;
1354 self.chain_write(chain_id, move |mut guard| async move {
1355 guard
1356 .stage_block_execution(block, round, &published_blobs, policy)
1357 .await
1358 })
1359 .await
1360 }
1361
1362 #[instrument(level = "trace", skip(self, chain_id, query))]
1367 pub async fn query_application(
1368 &self,
1369 chain_id: ChainId,
1370 query: Query,
1371 block_hash: Option<CryptoHash>,
1372 ) -> Result<(QueryOutcome, BlockHeight), WorkerError> {
1373 self.chain_write(chain_id, move |mut guard| async move {
1374 guard.query_application(query, block_hash).await
1375 })
1376 .await
1377 }
1378
1379 #[instrument(level = "trace", skip(self, chain_id, application_id), fields(
1380 nickname = %self.nickname(),
1381 chain_id = %chain_id,
1382 application_id = %application_id
1383 ))]
1384 pub async fn describe_application(
1386 &self,
1387 chain_id: ChainId,
1388 application_id: ApplicationId,
1389 ) -> Result<ApplicationDescription, WorkerError> {
1390 let state = self.get_or_create_chain_worker(chain_id).await?;
1391 let guard = handle::read_lock_initialized(&state).await?;
1392 guard.describe_application_readonly(application_id).await
1393 }
1394
1395 #[instrument(
1397 level = "trace",
1398 skip(self, certificate, notify_when_messages_are_delivered),
1399 fields(
1400 nickname = %self.nickname(),
1401 chain_id = %certificate.block().header.chain_id,
1402 block_height = %certificate.block().header.height
1403 )
1404 )]
1405 async fn process_confirmed_block(
1406 &self,
1407 certificate: ConfirmedBlockCertificate,
1408 mode: ProcessConfirmedBlockMode,
1409 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1410 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
1411 let chain_id = certificate.block().header.chain_id;
1412 self.chain_write(chain_id, move |mut guard| async move {
1413 guard
1414 .process_confirmed_block(certificate, mode, notify_when_messages_are_delivered)
1415 .await
1416 })
1417 .await
1418 }
1419
1420 #[instrument(level = "trace", skip(self, certificate), fields(
1422 nickname = %self.nickname(),
1423 chain_id = %certificate.block().header.chain_id,
1424 block_height = %certificate.block().header.height
1425 ))]
1426 async fn process_validated_block(
1427 &self,
1428 certificate: ValidatedBlockCertificate,
1429 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
1430 let chain_id = certificate.block().header.chain_id;
1431 self.chain_write(chain_id, move |mut guard| async move {
1432 guard.process_validated_block(certificate).await
1433 })
1434 .await
1435 }
1436
1437 #[instrument(level = "trace", skip(self, certificate), fields(
1439 nickname = %self.nickname(),
1440 chain_id = %certificate.value().chain_id(),
1441 height = %certificate.value().height()
1442 ))]
1443 async fn process_timeout(
1444 &self,
1445 certificate: TimeoutCertificate,
1446 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
1447 let chain_id = certificate.value().chain_id();
1448 self.chain_write(chain_id, move |mut guard| async move {
1449 guard.process_timeout(certificate).await
1450 })
1451 .await
1452 }
1453
1454 #[instrument(level = "trace", skip(self, origin, recipient, bundles), fields(
1457 nickname = %self.nickname(),
1458 origin = %origin,
1459 recipient = %recipient,
1460 num_bundles = %bundles.len()
1461 ))]
1462 async fn process_cross_chain_update(
1463 &self,
1464 origin: ChainId,
1465 recipient: ChainId,
1466 bundles: Vec<(Epoch, MessageBundle)>,
1467 previous_height: Option<BlockHeight>,
1468 ) -> Result<CrossChainUpdateResult, WorkerError> {
1469 let (result_sender, receiver) = oneshot::channel();
1470 let request = BatchRequest::Update {
1471 origin,
1472 bundles,
1473 previous_height,
1474 result_sender,
1475 };
1476 self.enqueue_and_drive(recipient, request, receiver).await
1477 }
1478
1479 async fn confirm_updated_recipient(
1482 &self,
1483 sender: ChainId,
1484 recipient: ChainId,
1485 latest_height: BlockHeight,
1486 ) -> Result<NetworkActions, WorkerError> {
1487 let (result_sender, receiver) = oneshot::channel();
1488 let request = BatchRequest::Confirm {
1489 recipient,
1490 latest_height,
1491 result_sender,
1492 };
1493 self.enqueue_and_drive(sender, request, receiver).await
1494 }
1495
1496 async fn enqueue_and_drive<R>(
1499 &self,
1500 chain_id: ChainId,
1501 request: BatchRequest,
1502 mut receiver: oneshot::Receiver<Result<R, WorkerError>>,
1503 ) -> Result<R, WorkerError> {
1504 let mut pending = Some(request);
1505 loop {
1506 let (sender, future) = self.get_or_create_chain_batch(chain_id).await?;
1507 if let Some(request) = pending.take() {
1508 if let Err(mpsc::error::SendError(request)) = sender.send(request) {
1509 pending = Some(request);
1510 continue; }
1512 }
1513 match future::select(pin::pin!(&mut receiver), future).await {
1516 Either::Left((result, _)) => {
1517 return result.unwrap_or(Err(WorkerError::PoisonedWorker));
1523 }
1524 Either::Right(((), _)) => match receiver.try_recv() {
1525 Ok(result) => return result,
1526 Err(oneshot::error::TryRecvError::Empty) => {}
1527 Err(oneshot::error::TryRecvError::Closed) => {
1528 return Err(WorkerError::ChainError(Box::new(
1529 ChainError::InternalError("batch driver stopped".into()),
1530 )));
1531 }
1532 },
1533 }
1534 }
1535 }
1536
1537 #[instrument(level = "trace", skip(self, chain_id, height), fields(
1539 nickname = %self.nickname(),
1540 chain_id = %chain_id,
1541 height = %height
1542 ))]
1543 #[cfg(with_testing)]
1544 pub async fn read_certificate(
1545 &self,
1546 chain_id: ChainId,
1547 height: BlockHeight,
1548 ) -> Result<Option<CacheArc<ConfirmedBlockCertificate>>, WorkerError> {
1549 let state = self.get_or_create_chain_worker(chain_id).await?;
1550 let guard = handle::read_lock_initialized(&state).await?;
1551 guard.read_certificate(height).await
1552 }
1553
1554 #[cfg(with_testing)]
1557 pub async fn select_message_bundles(
1558 &self,
1559 recipient: ChainId,
1560 origin: &ChainId,
1561 next_height_to_receive: BlockHeight,
1562 last_anticipated_block_height: Option<BlockHeight>,
1563 bundles: Vec<(Epoch, MessageBundle)>,
1564 ) -> Result<Vec<MessageBundle>, WorkerError> {
1565 let state = self.get_or_create_chain_worker(recipient).await?;
1566 let guard = handle::read_lock(&state).await?;
1567 guard
1568 .select_message_bundles(
1569 origin,
1570 next_height_to_receive,
1571 last_anticipated_block_height,
1572 bundles,
1573 )
1574 .await
1575 }
1576
1577 #[cfg(with_testing)]
1580 pub async fn reset_and_reexecute_chain(
1581 &self,
1582 chain_id: ChainId,
1583 ) -> Result<Vec<CrossChainRequest>, WorkerError> {
1584 let state = self.get_or_create_chain_worker(chain_id).await?;
1585 let mut guard = handle::write_lock(&state).await?;
1586 guard.reset_and_reexecute_chain().await
1587 }
1588
1589 #[instrument(level = "trace", skip(self), fields(
1595 nickname = %self.nickname(),
1596 chain_id = %chain_id
1597 ))]
1598 pub async fn chain_state_view(
1599 &self,
1600 chain_id: ChainId,
1601 ) -> Result<ChainStateViewReadGuard<StorageClient>, WorkerError> {
1602 let state = self.get_or_create_chain_worker(chain_id).await?;
1603 let guard = handle::read_lock(&state).await?;
1604 Ok(ChainStateViewReadGuard(OwnedRwLockReadGuard::map(
1605 guard,
1606 |s| s.chain(),
1607 )))
1608 }
1609
1610 #[instrument(skip_all, fields(
1611 nick = self.nickname(),
1612 chain_id = format!("{:.8}", proposal.content.block.chain_id),
1613 height = %proposal.content.block.height,
1614 ))]
1615 pub async fn handle_block_proposal(
1617 &self,
1618 proposal: BlockProposal,
1619 ) -> (Result<ChainInfoResponse, WorkerError>, NetworkActions) {
1620 trace!("{} <-- {:?}", self.nickname(), proposal);
1621 #[cfg(with_metrics)]
1622 let round = proposal.content.round;
1623
1624 let chain_id = proposal.content.block.chain_id;
1625 let now = self.storage.clock().current_time();
1627 let block_timestamp = proposal.content.block.timestamp;
1628 let delta = block_timestamp.delta_since(now);
1629 let grace_period = TimeDelta::from_micros(
1630 u64::try_from(self.chain_worker_config.block_time_grace_period.as_micros())
1631 .unwrap_or(u64::MAX),
1632 );
1633 if delta > TimeDelta::ZERO && delta <= grace_period {
1634 self.storage.clock().sleep_until(block_timestamp).await;
1635 }
1636
1637 let outcome = self
1638 .chain_write(chain_id, move |mut guard| async move {
1639 Ok::<_, WorkerError>(guard.handle_block_proposal(proposal).await)
1640 })
1641 .await;
1642 let (result, actions) = match outcome {
1643 Ok((result, actions)) => (result, actions),
1644 Err(err) => (Err(err), NetworkActions::default()),
1645 };
1646 #[cfg(with_metrics)]
1647 if result.is_ok() {
1648 metrics::NUM_ROUNDS_IN_BLOCK_PROPOSAL
1649 .with_label_values(&[round.type_name()])
1650 .observe(round.number() as f64);
1651 }
1652 (result, actions)
1653 }
1654
1655 #[instrument(skip_all, fields(
1658 chain_id = %certificate.value.chain_id,
1659 hash = %certificate.value.value_hash,
1660 ))]
1661 pub async fn handle_lite_certificate(
1662 &self,
1663 certificate: LiteCertificate<'_>,
1664 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1665 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
1666 match self.full_certificate(certificate).await? {
1667 Either::Left(confirmed) => {
1668 Box::pin(self.handle_confirmed_certificate(
1669 confirmed,
1670 ProcessConfirmedBlockMode::Auto,
1671 notify_when_messages_are_delivered,
1672 ))
1673 .await
1674 }
1675 Either::Right(validated) => {
1676 if let Some(notifier) = notify_when_messages_are_delivered {
1677 if let Err(()) = notifier.send(()) {
1679 debug!("Failed to notify message delivery to caller (validation cert)");
1680 }
1681 }
1682 Box::pin(self.handle_validated_certificate(validated)).await
1683 }
1684 }
1685 }
1686
1687 #[instrument(skip_all, fields(
1689 nick = self.nickname(),
1690 chain_id = format!("{:.8}", certificate.block().header.chain_id),
1691 height = %certificate.block().header.height,
1692 ))]
1693 pub async fn handle_confirmed_certificate(
1694 &self,
1695 certificate: ConfirmedBlockCertificate,
1696 mode: ProcessConfirmedBlockMode,
1697 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1698 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
1699 trace!("{} <-- {:?}", self.nickname(), certificate);
1700 #[cfg(with_metrics)]
1701 let metrics_data = metrics::MetricsData::new(&certificate);
1702
1703 #[allow(unused_variables)]
1704 let (info, actions, outcome) = Box::pin(self.process_confirmed_block(
1705 certificate,
1706 mode,
1707 notify_when_messages_are_delivered,
1708 ))
1709 .await?;
1710
1711 #[cfg(with_metrics)]
1712 if matches!(outcome, BlockOutcome::Processed) {
1713 metrics_data.record();
1714 }
1715 Ok((info, actions))
1716 }
1717
1718 #[instrument(skip_all, fields(
1720 nick = self.nickname(),
1721 chain_id = format!("{:.8}", certificate.block().header.chain_id),
1722 height = %certificate.block().header.height,
1723 ))]
1724 pub async fn handle_validated_certificate(
1725 &self,
1726 certificate: ValidatedBlockCertificate,
1727 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
1728 trace!("{} <-- {:?}", self.nickname(), certificate);
1729
1730 #[cfg(with_metrics)]
1731 let round = certificate.round;
1732 #[cfg(with_metrics)]
1733 let cert_str = certificate.inner().to_log_str();
1734
1735 #[allow(unused_variables)]
1736 let (info, actions, outcome) = Box::pin(self.process_validated_block(certificate)).await?;
1737 #[cfg(with_metrics)]
1738 {
1739 if matches!(outcome, BlockOutcome::Processed) {
1740 metrics::NUM_ROUNDS_IN_CERTIFICATE
1741 .with_label_values(&[cert_str, round.type_name()])
1742 .observe(round.number() as f64);
1743 }
1744 }
1745 Ok((info, actions))
1746 }
1747
1748 #[instrument(skip_all, fields(
1750 nick = self.nickname(),
1751 chain_id = format!("{:.8}", certificate.inner().chain_id()),
1752 height = %certificate.inner().height(),
1753 ))]
1754 pub async fn handle_timeout_certificate(
1755 &self,
1756 certificate: TimeoutCertificate,
1757 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
1758 trace!("{} <-- {:?}", self.nickname(), certificate);
1759 self.process_timeout(certificate).await
1760 }
1761
1762 #[instrument(skip_all, fields(
1763 nick = self.nickname(),
1764 chain_id = format!("{:.8}", query.chain_id)
1765 ))]
1766 pub async fn handle_chain_info_query(
1768 &self,
1769 query: ChainInfoQuery,
1770 ) -> Result<ChainInfoResponse, WorkerError> {
1771 trace!("{} <-- {:?}", self.nickname(), query);
1772 #[cfg(with_metrics)]
1773 metrics::CHAIN_INFO_QUERIES.inc();
1774 let chain_id = query.chain_id;
1775 let result = self
1776 .chain_write(chain_id, move |mut guard| async move {
1777 guard.handle_chain_info_query(query).await
1778 })
1779 .await;
1780 trace!("{} --> {:?}", self.nickname(), result);
1781 result
1782 }
1783
1784 #[instrument(skip_all, fields(
1785 nick = self.nickname(),
1786 chain_id = format!("{:.8}", chain_id)
1787 ))]
1788 pub async fn download_pending_blob(
1790 &self,
1791 chain_id: ChainId,
1792 blob_id: BlobId,
1793 ) -> Result<CacheArc<Blob>, WorkerError> {
1794 trace!("{} <-- download_pending_blob({blob_id:8})", self.nickname());
1795 let result = self
1796 .chain_read(chain_id, |guard| async move {
1797 guard.download_pending_blob(blob_id).await
1798 })
1799 .await;
1800 trace!(
1801 "{} --> {:?}",
1802 self.nickname(),
1803 result.as_ref().map(|_| blob_id)
1804 );
1805 result
1806 }
1807
1808 #[instrument(skip_all, fields(
1809 nick = self.nickname(),
1810 chain_id = format!("{:.8}", chain_id)
1811 ))]
1812 pub async fn handle_pending_blob(
1814 &self,
1815 chain_id: ChainId,
1816 blob: Blob,
1817 ) -> Result<ChainInfoResponse, WorkerError> {
1818 let blob_id = blob.id();
1819 trace!("{} <-- handle_pending_blob({blob_id:8})", self.nickname());
1820 let result = self
1821 .chain_write(chain_id, move |mut guard| async move {
1822 guard.handle_pending_blob(blob).await
1823 })
1824 .await;
1825 trace!(
1826 "{} --> {:?}",
1827 self.nickname(),
1828 result.as_ref().map(|_| blob_id)
1829 );
1830 result
1831 }
1832
1833 #[instrument(skip_all, fields(
1834 nick = self.nickname(),
1835 chain_id = format!("{:.8}", request.target_chain_id())
1836 ))]
1837 pub async fn handle_cross_chain_request(
1839 &self,
1840 request: CrossChainRequest,
1841 ) -> Result<NetworkActions, WorkerError> {
1842 trace!("{} <-- {:?}", self.nickname(), request);
1843 match request {
1844 CrossChainRequest::UpdateRecipient {
1845 sender,
1846 recipient,
1847 bundles,
1848 previous_height,
1849 } => {
1850 let mut actions = NetworkActions::default();
1851 let origin = sender;
1852 match self
1853 .process_cross_chain_update(origin, recipient, bundles, previous_height)
1854 .await?
1855 {
1856 CrossChainUpdateResult::NothingToDo => {}
1857 CrossChainUpdateResult::Updated(height) => {
1858 actions.notifications.push(Notification {
1859 chain_id: recipient,
1860 reason: Reason::NewIncomingBundle { origin, height },
1861 });
1862 actions.cross_chain_requests.push(
1863 CrossChainRequest::ConfirmUpdatedRecipient {
1864 sender,
1865 recipient,
1866 latest_height: height,
1867 },
1868 );
1869 }
1870 CrossChainUpdateResult::GapDetected {
1871 origin,
1872 retransmit_from,
1873 } => {
1874 actions
1875 .cross_chain_requests
1876 .push(CrossChainRequest::RevertConfirm {
1877 sender: origin,
1878 recipient,
1879 retransmit_from,
1880 });
1881 }
1882 }
1883 Ok(actions)
1884 }
1885 CrossChainRequest::ConfirmUpdatedRecipient {
1886 sender,
1887 recipient,
1888 latest_height,
1889 } => {
1890 let actions = self
1891 .confirm_updated_recipient(sender, recipient, latest_height)
1892 .await?;
1893 Ok(actions)
1894 }
1895 CrossChainRequest::RevertConfirm {
1896 sender,
1897 recipient,
1898 retransmit_from,
1899 } => {
1900 self.chain_write(sender, move |mut guard| async move {
1901 guard
1902 .handle_revert_confirm(recipient, retransmit_from)
1903 .await
1904 })
1905 .await
1906 }
1907 }
1908 }
1909
1910 #[instrument(skip_all, fields(
1912 nickname = %self.nickname(),
1913 chain_id = %chain_id,
1914 num_trackers = %new_trackers.len()
1915 ))]
1916 pub async fn update_received_certificate_trackers(
1917 &self,
1918 chain_id: ChainId,
1919 new_trackers: BTreeMap<ValidatorPublicKey, u64>,
1920 ) -> Result<(), WorkerError> {
1921 self.chain_write(chain_id, move |mut guard| async move {
1922 guard
1923 .update_received_certificate_trackers(new_trackers)
1924 .await
1925 })
1926 .await
1927 }
1928
1929 #[instrument(skip_all, fields(
1931 nickname = %self.nickname(),
1932 chain_id = %chain_id,
1933 start = %start,
1934 end = %end
1935 ))]
1936 pub async fn get_preprocessed_block_hashes(
1937 &self,
1938 chain_id: ChainId,
1939 start: BlockHeight,
1940 end: BlockHeight,
1941 ) -> Result<Vec<CryptoHash>, WorkerError> {
1942 self.chain_read(chain_id, |guard| async move {
1943 guard.get_preprocessed_block_hashes(start, end).await
1944 })
1945 .await
1946 }
1947
1948 #[instrument(skip_all, fields(
1950 nickname = %self.nickname(),
1951 chain_id = %chain_id,
1952 origin = %origin
1953 ))]
1954 pub async fn get_inbox_next_height(
1955 &self,
1956 chain_id: ChainId,
1957 origin: ChainId,
1958 ) -> Result<BlockHeight, WorkerError> {
1959 self.chain_read(chain_id, |guard| async move {
1960 guard.get_inbox_next_height(origin).await
1961 })
1962 .await
1963 }
1964
1965 #[instrument(skip_all, fields(
1968 nickname = %self.nickname(),
1969 chain_id = %chain_id,
1970 num_blob_ids = %blob_ids.len()
1971 ))]
1972 pub async fn get_locking_blobs(
1973 &self,
1974 chain_id: ChainId,
1975 blob_ids: Vec<BlobId>,
1976 ) -> Result<Option<Vec<Blob>>, WorkerError> {
1977 self.chain_read(chain_id, |guard| async move {
1978 guard.get_locking_blobs(blob_ids).await
1979 })
1980 .await
1981 }
1982
1983 pub async fn get_block_hashes(
1985 &self,
1986 chain_id: ChainId,
1987 heights: Vec<BlockHeight>,
1988 ) -> Result<Vec<CryptoHash>, WorkerError> {
1989 self.chain_read(chain_id, |guard| async move {
1990 guard.get_block_hashes(heights).await
1991 })
1992 .await
1993 }
1994
1995 pub async fn get_proposed_blobs(
1997 &self,
1998 chain_id: ChainId,
1999 blob_ids: Vec<BlobId>,
2000 ) -> Result<Vec<Blob>, WorkerError> {
2001 self.chain_read(chain_id, |guard| async move {
2002 guard.get_proposed_blobs(blob_ids).await
2003 })
2004 .await
2005 }
2006
2007 pub async fn get_event_subscriptions(
2009 &self,
2010 chain_id: ChainId,
2011 ) -> Result<EventSubscriptionsResult, WorkerError> {
2012 self.chain_read(chain_id, |guard| async move {
2013 guard.get_event_subscriptions().await
2014 })
2015 .await
2016 }
2017
2018 pub async fn get_stream_indices(
2021 &self,
2022 chain_id: ChainId,
2023 stream_id: StreamId,
2024 ) -> Result<StreamCounts, WorkerError> {
2025 self.chain_read(chain_id, |guard| async move {
2026 guard.get_stream_indices(stream_id).await
2027 })
2028 .await
2029 }
2030
2031 pub async fn next_expected_events(
2033 &self,
2034 chain_id: ChainId,
2035 stream_ids: Vec<StreamId>,
2036 ) -> Result<BTreeMap<StreamId, u32>, WorkerError> {
2037 self.chain_read(chain_id, |guard| async move {
2038 guard.get_next_expected_events(stream_ids).await
2039 })
2040 .await
2041 }
2042
2043 pub async fn get_received_certificate_trackers(
2045 &self,
2046 chain_id: ChainId,
2047 ) -> Result<HashMap<ValidatorPublicKey, u64>, WorkerError> {
2048 self.chain_read(chain_id, |guard| async move {
2049 guard.get_received_certificate_trackers().await
2050 })
2051 .await
2052 }
2053
2054 pub async fn cross_chain_network_actions(
2058 &self,
2059 chain_id: ChainId,
2060 ) -> Result<NetworkActions, WorkerError> {
2061 if let Some(actions) = self
2065 .chain_read(chain_id, |guard| async move {
2066 guard.cross_chain_network_actions_if_reconciled().await
2067 })
2068 .await?
2069 {
2070 return Ok(actions);
2071 }
2072 self.chain_write(chain_id, |mut guard| async move {
2075 guard.reconcile_and_cross_chain_network_actions().await
2076 })
2077 .await
2078 }
2079
2080 pub async fn get_tip_state_and_outbox_info(
2082 &self,
2083 chain_id: ChainId,
2084 receiver_id: ChainId,
2085 ) -> Result<(BlockHeight, Option<BlockHeight>), WorkerError> {
2086 self.chain_read(chain_id, |guard| async move {
2087 guard.get_tip_state_and_outbox_info(receiver_id).await
2088 })
2089 .await
2090 }
2091
2092 pub async fn get_next_height_to_preprocess(
2094 &self,
2095 chain_id: ChainId,
2096 ) -> Result<BlockHeight, WorkerError> {
2097 self.chain_read(chain_id, |guard| async move {
2098 Ok(guard.get_next_height_to_preprocess())
2099 })
2100 .await
2101 }
2102}
2103
2104#[cfg(with_testing)]
2105impl<StorageClient> WorkerState<StorageClient>
2106where
2107 StorageClient: Storage + Clone + 'static,
2108{
2109 #[instrument(level = "trace", skip(self))]
2115 pub fn public_key(&self) -> ValidatorPublicKey {
2116 self.chain_worker_config
2117 .key_pair()
2118 .expect(
2119 "Test validator should have a key pair assigned to it \
2120 in order to obtain its public key",
2121 )
2122 .public()
2123 }
2124}