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