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