1use std::{
7 borrow::Cow,
8 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
9 sync::{self, Arc},
10};
11
12use futures::future::Either;
13#[cfg(with_metrics)]
14use linera_base::prometheus_util::MeasureLatency as _;
15use linera_base::{
16 crypto::{CryptoHash, ValidatorPublicKey},
17 data_types::{
18 ApplicationDescription, ArithmeticError, Blob, BlockHeight, Epoch, OracleResponse, Round,
19 Timestamp,
20 },
21 ensure,
22 hashed::Hashed,
23 identifiers::{AccountOwner, ApplicationId, BlobId, BlobType, ChainId, EventId, StreamId},
24};
25use linera_cache::{Arc as CacheArc, UniqueValueCache, ValueCache};
26use linera_chain::{
27 data_types::{
28 BlockProposal, BundleExecutionPolicy, IncomingBundle, MessageAction, MessageBundle,
29 OriginalProposal, ProposalContent, ProposedBlock,
30 },
31 manager::{self, ManagerSafetySnapshot},
32 types::{
33 Block, ConfirmedBlock, ConfirmedBlockCertificate, TimeoutCertificate,
34 ValidatedBlockCertificate,
35 },
36 BlockExecution, ChainError, ChainExecutionContext, ChainIdSet, ChainStateView, ChainTipState,
37 ExecutionResultExt as _, StreamCounts,
38};
39use linera_execution::{
40 system::{EpochEventData, EventSubscriptions, EPOCH_STREAM_NAME},
41 ExecutionRuntimeContext as _, ExecutionStateView, Query, QueryContext, QueryOutcome,
42 ResourceTracker, ServiceRuntimeEndpoint,
43};
44use linera_storage::{Clock as _, Storage};
45use linera_views::{
46 batch::Batch,
47 context::{Context, InactiveContext},
48 store::WritableKeyValueStore as _,
49 views::{ReplaceContext as _, RootView as _, View as _},
50};
51use tokio::sync::oneshot;
52use tracing::{debug, info, instrument, trace, warn};
53
54use crate::{
55 chain_worker::{
56 export::BlockExportHandle, handle::AtomicTimestamp, ChainWorkerConfig, DeliveryNotifier,
57 },
58 client::{ChainModes, ListeningMode},
59 data_types::{ChainInfo, ChainInfoQuery, ChainInfoResponse, CrossChainRequest},
60 worker::{BatchRequest, NetworkActions, Notification, Reason, WorkerError},
61};
62
63pub(crate) type EventSubscriptionsResult = Vec<((ChainId, StreamId), EventSubscriptions)>;
65
66#[cfg(with_metrics)]
67pub(crate) mod metrics {
68 use linera_base::prometheus_util::{
69 exponential_bucket_interval, exponential_bucket_latencies, register_histogram,
70 register_histogram_vec, register_int_counter, register_int_counter_vec,
71 };
72 use prometheus::{Histogram, HistogramVec, IntCounter, IntCounterVec};
73
74 linera_base::declare_metrics! {
75 pub static CREATE_NETWORK_ACTIONS_LATENCY: Histogram =
76 register_histogram(
77 "create_network_actions_latency",
78 "Time (ms) to create network actions",
79 exponential_bucket_latencies(10_000.0),
80 );
81
82 pub static NUM_INBOXES: HistogramVec =
83 register_histogram_vec(
84 "num_inboxes",
85 "Number of inboxes",
86 &[],
87 exponential_bucket_interval(1.0, 10_000.0),
88 );
89
90 pub static RECEIVED_LOG_QUERY_ENTRIES: Histogram =
91 register_histogram(
92 "received_log_query_entries",
93 "Number of received-log entries returned per chain info query that asks for them",
94 exponential_bucket_interval(1.0, 100_000.0),
95 );
96
97 pub static BLOCK_PROPOSALS_RECEIVED_TOTAL: IntCounter =
98 register_int_counter(
99 "block_proposals_received_total",
100 "Total number of block proposals received by the worker",
101 );
102
103 pub static BLOCK_PROPOSALS_REJECTED_TOTAL: IntCounterVec =
104 register_int_counter_vec(
105 "block_proposals_rejected_total",
106 "Total number of block proposals rejected by the worker, labelled by error type",
107 &["error_type"],
108 );
109 }
110}
111
112pub(crate) struct ChainWorkerState<StorageClient>
114where
115 StorageClient: Storage,
116{
117 config: ChainWorkerConfig,
118 storage: StorageClient,
119 chain: ChainStateView<StorageClient::Context>,
120 service_runtime_endpoint: Option<ServiceRuntimeEndpoint>,
121 service_runtime_task: Option<web_thread_pool::Task<()>>,
126 last_access: Arc<AtomicTimestamp>,
131 block_values: Arc<ValueCache<CryptoHash, ConfirmedBlock>>,
132 execution_state_cache:
133 Option<Arc<UniqueValueCache<CryptoHash, ExecutionStateView<InactiveContext>>>>,
134 chain_modes: Option<Arc<sync::RwLock<ChainModes>>>,
135 delivery_notifier: DeliveryNotifier,
136 knows_chain_is_active: bool,
137 poisoned: bool,
140 block_export: Option<BlockExportHandle>,
142 last_exported_heights_fold: Option<linera_base::time::Instant>,
145}
146
147pub(crate) enum CrossChainUpdateResult {
149 Updated(BlockHeight),
151 NothingToDo,
153 GapDetected {
157 origin: ChainId,
158 retransmit_from: BlockHeight,
159 },
160}
161
162pub enum BlockOutcome {
164 Processed,
165 Preprocessed,
166 Skipped,
167}
168
169#[derive(Clone, Copy, Debug, Eq, PartialEq)]
171pub enum ProcessConfirmedBlockMode {
172 Auto,
176 Execute,
180 Preprocess,
184}
185
186impl<StorageClient> ChainWorkerState<StorageClient>
187where
188 StorageClient: Storage + Clone + 'static,
189{
190 #[instrument(skip_all, fields(
192 chain_id = %chain_id
193 ))]
194 #[expect(clippy::too_many_arguments)]
195 pub(crate) async fn load(
196 config: ChainWorkerConfig,
197 storage: StorageClient,
198 block_values: Arc<ValueCache<CryptoHash, ConfirmedBlock>>,
199 execution_state_cache: Option<
200 Arc<UniqueValueCache<CryptoHash, ExecutionStateView<InactiveContext>>>,
201 >,
202 chain_modes: Option<Arc<sync::RwLock<ChainModes>>>,
203 delivery_notifier: DeliveryNotifier,
204 chain_id: ChainId,
205 service_runtime_endpoint: Option<ServiceRuntimeEndpoint>,
206 service_runtime_task: Option<web_thread_pool::Task<()>>,
207 block_export: Option<BlockExportHandle>,
208 ) -> Result<Self, WorkerError> {
209 let chain = storage.load_chain(chain_id).await?;
210
211 Ok(ChainWorkerState {
212 config,
213 storage,
214 chain,
215 service_runtime_endpoint,
216 service_runtime_task,
217 last_access: Arc::new(AtomicTimestamp::now()),
218 block_values,
219 execution_state_cache,
220 chain_modes,
221 delivery_notifier,
222 knows_chain_is_active: false,
223 poisoned: false,
224 block_export,
225 last_exported_heights_fold: None,
226 })
227 }
228
229 fn chain_id(&self) -> ChainId {
231 self.chain.chain_id()
232 }
233
234 pub(crate) fn chain(&self) -> &ChainStateView<StorageClient::Context> {
236 &self.chain
237 }
238
239 async fn committee_for_epoch(
246 &self,
247 epoch: Epoch,
248 ) -> Result<linera_execution::committee::Committee, WorkerError> {
249 let hash = self
250 .chain
251 .execution_state
252 .context()
253 .extra()
254 .get_committee_hashes(epoch..=epoch)
255 .await
256 .map_err(|error| {
257 ChainError::ExecutionError(Box::new(error), ChainExecutionContext::Block)
258 })?
259 .remove(&epoch)
260 .ok_or_else(|| {
261 ChainError::InternalError(format!(
262 "missing committee for epoch {epoch}; this is a bug"
263 ))
264 })?;
265 let committee = self
266 .chain
267 .execution_state
268 .context()
269 .extra()
270 .get_or_load_committee_by_hash(hash)
271 .await
272 .map_err(|error| {
273 ChainError::ExecutionError(Box::new(error), ChainExecutionContext::Block)
274 })?;
275 Ok((*committee).clone())
276 }
277
278 pub(crate) async fn select_message_bundles(
286 &self,
287 origin: &ChainId,
288 next_height_to_receive: BlockHeight,
289 last_anticipated_block_height: Option<BlockHeight>,
290 mut bundles: Vec<(Epoch, MessageBundle)>,
291 ) -> Result<Vec<MessageBundle>, WorkerError> {
292 let recipient = self.chain_id();
293 let mut latest_height = None;
294 let mut skipped_len = 0;
295 let mut trusted_len = 0;
296 for (i, (epoch, bundle)) in bundles.iter().enumerate() {
297 ensure!(
298 latest_height <= Some(bundle.height),
299 WorkerError::InvalidCrossChainRequest
300 );
301 latest_height = Some(bundle.height);
302 if bundle.height < next_height_to_receive {
303 skipped_len = i + 1;
304 }
305 let is_revoked = self
306 .storage
307 .is_epoch_revoked(*epoch)
308 .await
309 .map_err(|error| {
310 WorkerError::ChainError(Box::new(ChainError::ExecutionError(
311 Box::new(error),
312 ChainExecutionContext::Block,
313 )))
314 })?;
315 if !is_revoked || Some(bundle.height) <= last_anticipated_block_height {
316 trusted_len = i + 1;
317 }
318 }
319 if skipped_len > 0 {
320 let (_, sample_bundle) = &bundles[skipped_len - 1];
321 debug!(
322 "Ignoring repeated messages to {recipient:.8} from {origin:} at height {}",
323 sample_bundle.height,
324 );
325 }
326 if skipped_len < bundles.len() && trusted_len < bundles.len() {
327 let (sample_epoch, sample_bundle) = &bundles[trusted_len];
328 warn!(
329 "Refusing messages to {recipient:.8} from {origin:} at height {} \
330 because the epoch {} is not trusted any more",
331 sample_bundle.height, sample_epoch,
332 );
333 }
334 Ok(if skipped_len < trusted_len {
335 bundles
336 .drain(skipped_len..trusted_len)
337 .map(|(_, bundle)| bundle)
338 .collect()
339 } else {
340 vec![]
341 })
342 }
343
344 pub(crate) fn knows_chain_is_active(&self) -> bool {
346 self.knows_chain_is_active
347 }
348
349 pub(crate) fn rollback(&mut self) {
351 self.chain.rollback();
352 }
353
354 pub(crate) fn check_not_poisoned(&self) -> Result<(), WorkerError> {
357 ensure!(!self.poisoned, WorkerError::PoisonedWorker);
358 Ok(())
359 }
360
361 pub(crate) fn touch(&self) {
363 self.last_access.store_now();
364 }
365
366 pub(crate) fn last_access_arc(&self) -> Arc<AtomicTimestamp> {
368 Arc::clone(&self.last_access)
369 }
370
371 pub(crate) fn clear_service_runtime(&mut self) -> Option<web_thread_pool::Task<()>> {
374 self.service_runtime_endpoint.take();
375 self.service_runtime_task.take()
376 }
377
378 pub(crate) async fn cross_chain_network_actions_if_reconciled(
382 &self,
383 ) -> Result<Option<NetworkActions>, WorkerError> {
384 let tracked = self.tracked_full_chains();
385 if !self.chain.outbox_index_is_reconciled(tracked.as_deref()) {
386 return Ok(None);
387 }
388 Ok(Some(
389 self.build_network_actions(None, tracked.as_deref().map(|h| h.inner()))
390 .await?,
391 ))
392 }
393
394 #[instrument(skip_all, fields(chain_id = %self.chain_id()))]
401 pub(crate) async fn reconcile_and_cross_chain_network_actions(
402 &mut self,
403 ) -> Result<NetworkActions, WorkerError> {
404 let tracked = self.tracked_full_chains();
405 self.chain
406 .reconcile_outbox_index(tracked.as_deref())
407 .await?;
408 let actions = self
409 .build_network_actions(None, tracked.as_deref().map(|h| h.inner()))
410 .await?;
411 self.save().await?;
412 Ok(actions)
413 }
414
415 #[tracing::instrument(level = "debug", skip(self))]
417 pub(crate) async fn handle_chain_info_query(
418 &mut self,
419 query: ChainInfoQuery,
420 ) -> Result<ChainInfoResponse, WorkerError> {
421 if let Some((height, round)) = query.request_leader_timeout {
422 self.vote_for_leader_timeout(height, round).await?;
423 }
424 if query.request_fallback {
425 self.vote_for_fallback().await?;
426 }
427 self.prepare_chain_info_response(query).await
428 }
429
430 #[instrument(skip_all, fields(
432 chain_id = %self.chain_id(),
433 blob_id = %blob_id
434 ))]
435 pub(crate) async fn download_pending_blob(
436 &self,
437 blob_id: BlobId,
438 ) -> Result<CacheArc<Blob>, WorkerError> {
439 if let Some(blob) = self.chain.manager.pending_blob(&blob_id).await? {
440 return Ok(self.storage.cache_blob(blob));
441 }
442 self.storage
443 .read_blob(blob_id)
444 .await?
445 .ok_or(WorkerError::BlobsNotFound(vec![blob_id]))
446 }
447
448 #[instrument(skip_all, fields(
451 chain_id = %self.chain_id()
452 ))]
453 async fn get_required_blobs(
454 &self,
455 required_blob_ids: impl IntoIterator<Item = BlobId>,
456 created_blobs: BTreeMap<BlobId, Blob>,
457 ) -> Result<BTreeMap<BlobId, Blob>, WorkerError> {
458 let maybe_blobs = self
459 .maybe_get_required_blobs(required_blob_ids, Some(created_blobs))
460 .await?;
461 let not_found_blob_ids = missing_blob_ids(&maybe_blobs);
462 ensure!(
463 not_found_blob_ids.is_empty(),
464 WorkerError::BlobsNotFound(not_found_blob_ids)
465 );
466 Ok(maybe_blobs
467 .into_iter()
468 .filter_map(|(blob_id, maybe_blob)| Some((blob_id, maybe_blob?)))
469 .collect())
470 }
471
472 #[instrument(skip_all, fields(
474 chain_id = %self.chain_id()
475 ))]
476 async fn maybe_get_required_blobs(
477 &self,
478 blob_ids: impl IntoIterator<Item = BlobId>,
479 mut created_blobs: Option<BTreeMap<BlobId, Blob>>,
480 ) -> Result<BTreeMap<BlobId, Option<Blob>>, WorkerError> {
481 let maybe_blobs = blob_ids.into_iter().collect::<BTreeSet<_>>();
482 let mut maybe_blobs = maybe_blobs
483 .into_iter()
484 .map(|x| (x, None))
485 .collect::<Vec<(BlobId, Option<Blob>)>>();
486
487 if let Some(blob_map) = &mut created_blobs {
488 for (blob_id, value) in &mut maybe_blobs {
489 if let Some(blob) = blob_map.remove(blob_id) {
490 *value = Some(blob);
491 }
492 }
493 }
494
495 let (missing_indices, missing_blob_ids) = missing_indices_blob_ids(&maybe_blobs);
496 let second_block_blobs = self.chain.manager.pending_blobs(&missing_blob_ids).await?;
497 for (index, blob) in missing_indices.into_iter().zip(second_block_blobs) {
498 maybe_blobs[index].1 = blob;
499 }
500
501 let (missing_indices, missing_blob_ids) = missing_indices_blob_ids(&maybe_blobs);
502 let third_block_blobs = self
503 .chain
504 .pending_validated_blobs
505 .multi_get(&missing_blob_ids)
506 .await?;
507 for (index, blob) in missing_indices.into_iter().zip(third_block_blobs) {
508 maybe_blobs[index].1 = blob;
509 }
510
511 let (missing_indices, missing_blob_ids) = missing_indices_blob_ids(&maybe_blobs);
512 if !missing_indices.is_empty() {
513 let all_entries_pending_blobs = self
514 .chain
515 .pending_proposed_blobs
516 .try_load_all_entries()
517 .await?;
518 for (index, blob_id) in missing_indices.into_iter().zip(missing_blob_ids) {
519 for (_, pending_blobs) in &all_entries_pending_blobs {
520 if let Some(blob) = pending_blobs.get(&blob_id).await? {
521 maybe_blobs[index].1 = Some(blob);
522 break;
523 }
524 }
525 }
526 }
527
528 let (missing_indices, missing_blob_ids) = missing_indices_blob_ids(&maybe_blobs);
529 let fourth_block_blobs = self.storage.read_blobs(&missing_blob_ids).await?;
530 for (index, blob) in missing_indices.into_iter().zip(fourth_block_blobs) {
531 maybe_blobs[index].1 = blob.map(CacheArc::unwrap_or_clone);
532 }
533 Ok(maybe_blobs.into_iter().collect())
534 }
535
536 #[instrument(skip_all, fields(
538 chain_id = %self.chain_id()
539 ))]
540 async fn create_cross_chain_actions_for_recipient(
541 &self,
542 recipient: ChainId,
543 ) -> Result<NetworkActions, WorkerError> {
544 let outbox = self.chain.outboxes.try_load_entry(&recipient).await?;
545 let Some(outbox) = outbox else {
546 return Ok(NetworkActions::default());
547 };
548 let heights = outbox.queue.elements().await?;
549 if heights.is_empty() {
550 return Ok(NetworkActions::default());
551 }
552 let heights_by_recipient = BTreeMap::from([(recipient, heights)]);
553 let cross_chain_requests = self
554 .create_cross_chain_requests(heights_by_recipient)
555 .await?;
556 Ok(NetworkActions {
557 cross_chain_requests,
558 notifications: Vec::new(),
559 })
560 }
561
562 fn tracked_full_chains(&self) -> Option<Arc<Hashed<ChainIdSet>>> {
565 let chain_modes = self.chain_modes.as_ref()?;
566 let full = chain_modes
567 .read()
568 .expect("Panics should not happen while holding a lock to `chain_modes`")
569 .full();
570 Some(full)
571 }
572
573 fn is_tracked(&self, chain_id: &ChainId) -> bool {
576 self.chain_modes.as_ref().is_none_or(|chain_modes| {
577 chain_modes
578 .read()
579 .expect("Panics should not happen while holding a lock to `chain_modes`")
580 .get(chain_id)
581 .is_some_and(ListeningMode::is_full)
582 })
583 }
584
585 async fn reconcile_tracked_outboxes(
588 &mut self,
589 ) -> Result<Option<Arc<Hashed<ChainIdSet>>>, WorkerError> {
590 let full_chains = self.tracked_full_chains();
591 self.chain
592 .reconcile_outbox_index(full_chains.as_deref())
593 .await?;
594 Ok(full_chains)
595 }
596
597 async fn create_network_actions(
600 &mut self,
601 old_round: Option<Round>,
602 ) -> Result<NetworkActions, WorkerError> {
603 let tracked = self.reconcile_tracked_outboxes().await?;
606 self.build_network_actions(old_round, tracked.as_deref().map(|h| h.inner()))
607 .await
608 }
609
610 async fn build_network_actions(
612 &self,
613 old_round: Option<Round>,
614 tracked: Option<&ChainIdSet>,
615 ) -> Result<NetworkActions, WorkerError> {
616 #[cfg(with_metrics)]
617 let _latency = metrics::CREATE_NETWORK_ACTIONS_LATENCY.measure_latency();
618 let mut heights_by_recipient = BTreeMap::<_, Vec<_>>::new();
619 let targets = self.chain.nonempty_outbox_chain_ids();
620 if let Some(tracked) = tracked {
621 if let Some(target) = targets.iter().find(|target| !tracked.contains(*target)) {
622 return Err(ChainError::CorruptedChainState(format!(
623 "outbox index contains untracked target {target}"
624 ))
625 .into());
626 }
627 }
628 let outboxes = self.chain.load_outboxes(&targets).await?;
629 for (target, outbox) in targets.into_iter().zip(outboxes) {
630 let heights = outbox.queue.elements().await?;
631 heights_by_recipient.insert(target, heights);
632 }
633 let cross_chain_requests = self
634 .create_cross_chain_requests(heights_by_recipient)
635 .await?;
636 let mut notifications = Vec::new();
637 if let Some(old_round) = old_round {
638 let round = self.chain.manager.current_round();
639 if round > old_round {
640 let height = self.chain.tip_state.get().next_block_height;
641 notifications.push(Notification {
642 chain_id: self.chain_id(),
643 reason: Reason::NewRound { height, round },
644 });
645 }
646 }
647 Ok(NetworkActions {
648 cross_chain_requests,
649 notifications,
650 })
651 }
652
653 async fn read_confirmed_blocks(
656 &self,
657 hashes: &[CryptoHash],
658 ) -> Result<Vec<Option<CacheArc<ConfirmedBlock>>>, WorkerError> {
659 let mut blocks = Vec::with_capacity(hashes.len());
660 let mut uncached_indices = Vec::new();
661 let mut uncached_hashes = Vec::new();
662
663 for (i, hash) in hashes.iter().enumerate() {
664 if let Some(block) = self.block_values.get(hash) {
665 blocks.push(Some(block));
666 } else {
667 blocks.push(None);
668 uncached_indices.push(i);
669 uncached_hashes.push(*hash);
670 }
671 }
672
673 if !uncached_hashes.is_empty() {
674 let from_storage = self.storage.read_confirmed_blocks(uncached_hashes).await?;
675 for (i, maybe_block) in uncached_indices.into_iter().zip(from_storage) {
676 blocks[i] = maybe_block;
677 }
678 }
679
680 Ok(blocks)
681 }
682
683 #[instrument(skip_all, fields(
684 chain_id = %self.chain_id(),
685 num_recipients = %heights_by_recipient.len()
686 ))]
687 async fn create_cross_chain_requests(
688 &self,
689 heights_by_recipient: BTreeMap<ChainId, Vec<BlockHeight>>,
690 ) -> Result<Vec<CrossChainRequest>, WorkerError> {
691 let heights = heights_by_recipient
693 .values()
694 .flatten()
695 .copied()
696 .collect::<BTreeSet<_>>();
697 let hashes = self
698 .chain
699 .block_hashes_for_heights(heights.iter().copied())
700 .await?;
701
702 let blocks = self.read_confirmed_blocks(&hashes).await?;
703
704 let mut height_to_blocks = HashMap::new();
705 for (block, hash) in blocks.into_iter().zip(hashes) {
706 let block = block.ok_or_else(|| WorkerError::ReadCertificatesError(vec![hash]))?;
707 height_to_blocks.insert(block.height(), block);
708 }
709
710 let sender = self.chain.chain_id();
711 let mut cross_chain_requests = Vec::new();
712 for (recipient, heights) in heights_by_recipient {
713 let previous_height = heights.first().and_then(|first_height| {
717 let block = height_to_blocks.get(first_height)?;
718 let (_, prev_height) =
719 block.block().body.previous_message_blocks.get(&recipient)?;
720 Some(*prev_height)
721 });
722 let mut bundles = Vec::new();
723 let mut bundles_size = 0;
724 for height in heights {
725 let Some(confirmed_block) = height_to_blocks.get(&height) else {
726 tracing::warn!(
727 %height,
728 %recipient,
729 "spurious entry in outbox; skipping this and higher sender blocks"
730 );
731 break;
732 };
733 let new_bundles = confirmed_block
734 .block()
735 .message_bundles_for(recipient, confirmed_block.inner().hash())
736 .collect::<Vec<_>>();
737 let new_size = new_bundles
738 .iter()
739 .map(|(_epoch, bundle)| bundle.estimated_size())
740 .sum::<usize>();
741 if bundles_size + new_size > self.config.cross_chain_message_chunk_limit {
744 if bundles.is_empty() {
745 warn!(
746 "Single block at height {height} produces an UpdateRecipient \
747 of ~{new_size} bytes, exceeding the chunk limit of {}",
748 self.config.cross_chain_message_chunk_limit
749 );
750 } else {
751 debug!(
752 "Stopping cross-chain batch for {recipient} at height {height}: \
753 adding ~{new_size} bytes would exceed chunk limit of {} \
754 (current batch ~{bundles_size} bytes)",
755 self.config.cross_chain_message_chunk_limit
756 );
757 break;
758 }
759 }
760 bundles.extend(new_bundles);
761 bundles_size += new_size;
762 }
763 if !bundles.is_empty() {
764 cross_chain_requests.push(CrossChainRequest::UpdateRecipient {
765 sender,
766 recipient,
767 bundles,
768 previous_height,
769 });
770 }
771 }
772 Ok(cross_chain_requests)
773 }
774
775 #[instrument(skip_all, fields(
777 chain_id = %self.chain_id(),
778 height = %certificate.inner().height()
779 ))]
780 pub(crate) async fn process_timeout(
781 &mut self,
782 certificate: TimeoutCertificate,
783 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
784 self.initialize_and_save_if_needed().await?;
787 let (chain_epoch, committee) = self.chain.current_committee().await?;
788 certificate.check(&committee)?;
789 if self
790 .chain
791 .tip_state
792 .get()
793 .already_validated_block(certificate.inner().height())?
794 {
795 return Ok((self.chain_info_response().await?, NetworkActions::default()));
796 }
797 ensure!(
798 certificate.inner().epoch() == chain_epoch,
799 WorkerError::InvalidEpoch {
800 chain_id: certificate.inner().chain_id(),
801 chain_epoch,
802 epoch: certificate.inner().epoch()
803 }
804 );
805 let old_round = self.chain.manager.current_round();
806 self.chain
807 .manager
808 .handle_timeout_certificate(certificate, self.storage.clock().current_time());
809 self.save().await?;
810 let actions = self.create_network_actions(Some(old_round)).await?;
811 Ok((self.chain_info_response().await?, actions))
812 }
813
814 #[instrument(skip_all, fields(
819 chain_id = %self.chain_id(),
820 block_height = %proposal.content.block.height
821 ))]
822 async fn load_proposal_blobs(
823 &mut self,
824 proposal: &BlockProposal,
825 ) -> Result<Vec<Blob>, WorkerError> {
826 let owner = proposal.owner();
827 let BlockProposal {
828 content:
829 ProposalContent {
830 block,
831 round,
832 outcome: _,
833 },
834 original_proposal,
835 signature: _,
836 } = proposal;
837
838 let mut maybe_blobs = self
839 .maybe_get_required_blobs(proposal.required_blob_ids(), None)
840 .await?;
841 let missing_blob_ids = missing_blob_ids(&maybe_blobs);
842 if !missing_blob_ids.is_empty() {
843 let chain = &mut self.chain;
844 if chain.ownership().await?.open_multi_leader_rounds {
845 chain.pending_proposed_blobs.clear();
847 }
848 let validated = matches!(original_proposal, Some(OriginalProposal::Regular { .. }));
849 chain
850 .pending_proposed_blobs
851 .try_load_entry_mut(&owner)
852 .await?
853 .update(*round, validated, maybe_blobs)?;
854 self.save().await?;
855 return Err(WorkerError::BlobsNotFound(missing_blob_ids));
856 }
857 let published_blobs = block
858 .published_blob_ids()
859 .iter()
860 .filter_map(|blob_id| maybe_blobs.remove(blob_id).flatten())
861 .collect::<Vec<_>>();
862 Ok(published_blobs)
863 }
864
865 #[instrument(skip_all, fields(
867 chain_id = %self.chain_id(),
868 block_height = %certificate.block().header.height
869 ))]
870 pub(crate) async fn process_validated_block(
871 &mut self,
872 certificate: ValidatedBlockCertificate,
873 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
874 let block = certificate.block();
875
876 let header = &block.header;
877 let height = header.height;
878 self.initialize_and_save_if_needed().await?;
881 let tip_state = self.chain.tip_state.get();
882 ensure!(
883 header.height == tip_state.next_block_height,
884 ChainError::UnexpectedBlockHeight {
885 expected_block_height: tip_state.next_block_height,
886 found_block_height: header.height,
887 }
888 );
889 let (epoch, committee) = self.chain.current_committee().await?;
890 check_block_epoch(epoch, header.chain_id, header.epoch)?;
891 certificate.check(&committee)?;
892 let already_committed_block = self.chain.tip_state.get().already_validated_block(height)?;
893 let should_skip_validated_block = || {
894 self.chain
895 .manager
896 .check_validated_block(&certificate)
897 .map(|outcome| outcome == manager::Outcome::Skip)
898 };
899 if already_committed_block || should_skip_validated_block()? {
900 return Ok((
902 self.chain_info_response().await?,
903 NetworkActions::default(),
904 BlockOutcome::Skipped,
905 ));
906 }
907
908 self.block_values
909 .insert_hashed(Cow::Borrowed(certificate.inner().inner()));
910 let required_blob_ids = block.required_blob_ids();
911 let maybe_blobs = self
912 .maybe_get_required_blobs(required_blob_ids, Some(block.created_blobs()))
913 .await?;
914 let missing_blob_ids = missing_blob_ids(&maybe_blobs);
915 if !missing_blob_ids.is_empty() {
916 self.chain
917 .pending_validated_blobs
918 .update(certificate.round, true, maybe_blobs)?;
919 self.save().await?;
920 return Err(WorkerError::BlobsNotFound(missing_blob_ids));
921 }
922 let blobs = maybe_blobs
923 .into_iter()
924 .filter_map(|(blob_id, maybe_blob)| Some((blob_id, maybe_blob?)))
925 .collect();
926 let old_round = self.chain.manager.current_round();
927 self.chain.manager.create_final_vote(
928 certificate,
929 self.config.key_pair(),
930 self.storage.clock().current_time(),
931 blobs,
932 )?;
933 self.save().await?;
934 let actions = self.create_network_actions(Some(old_round)).await?;
935 Ok((
936 self.chain_info_response().await?,
937 actions,
938 BlockOutcome::Processed,
939 ))
940 }
941
942 #[instrument(skip_all, fields(
944 chain_id = %certificate.block().header.chain_id,
945 height = %certificate.block().header.height,
946 block_hash = %certificate.hash(),
947 ))]
948 pub(crate) async fn process_confirmed_block(
949 &mut self,
950 certificate: ConfirmedBlockCertificate,
951 mode: ProcessConfirmedBlockMode,
952 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
953 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
954 let block = certificate.block();
955 let block_hash = certificate.hash();
956 let height = block.header.height;
957 let chain_id = block.header.chain_id;
958
959 let in_trust_set = self
967 .chain
968 .pre_checkpoint_block_trust
969 .contains(&block_hash)
970 .await?;
971 if in_trust_set {
972 self.chain.pre_checkpoint_block_trust.remove(&block_hash)?;
973 }
974
975 let tip = self.chain.tip_state.get().clone();
977 if !in_trust_set && tip.next_block_height > height {
978 let actions = self.create_network_actions(None).await?;
979 self.register_delivery_notifier(height, &actions, notify_when_messages_are_delivered)
980 .await;
981 return Ok((
982 self.chain_info_response().await?,
983 actions,
984 BlockOutcome::Skipped,
985 ));
986 }
987
988 let committee = self.committee_for_epoch(block.header.epoch).await?;
990 certificate.check(&committee)?;
991
992 let required_blob_ids = block.required_blob_ids();
996 let blobs_result = self
997 .get_required_blobs(required_blob_ids.iter().copied(), block.created_blobs())
998 .await
999 .map(|blobs| blobs.into_values().collect::<Vec<_>>());
1000
1001 if let Ok(blobs) = &blobs_result {
1002 self.storage
1003 .write_blobs_and_certificate(blobs, &certificate)
1004 .await?;
1005 let events = block
1006 .body
1007 .events
1008 .iter()
1009 .flatten()
1010 .map(|event| (event.id(chain_id), event.value.clone()));
1011 self.storage.write_events(events).await?;
1012 }
1013
1014 let blob_state = certificate.value().to_blob_state(blobs_result.is_ok());
1016 let blob_ids = required_blob_ids.into_iter().collect::<Vec<_>>();
1017 self.storage
1018 .maybe_write_blob_states(&blob_ids, blob_state)
1019 .await?;
1020
1021 let blobs = blobs_result?
1022 .into_iter()
1023 .map(|blob| (blob.id(), blob))
1024 .collect::<BTreeMap<_, _>>();
1025
1026 use ProcessConfirmedBlockMode::{Auto, Execute, Preprocess};
1034 let gap = tip.next_block_height != height;
1035 let starts_with_checkpoint = block.starts_with_checkpoint();
1036 match (mode, gap, starts_with_checkpoint) {
1037 (Preprocess, _, _) | (Auto, true, false) => {
1038 self.preprocess_certified_block(certificate, notify_when_messages_are_delivered)
1039 .await
1040 }
1041 (Execute, true, false) => Err(WorkerError::InvalidBlockChaining),
1042 (Auto | Execute, true, true) => {
1043 self.execute_block_with_checkpoint_restore(
1044 certificate,
1045 blobs,
1046 notify_when_messages_are_delivered,
1047 )
1048 .await
1049 }
1050 (Auto | Execute, false, _) => {
1051 self.execute_contiguous_block(
1052 certificate,
1053 blobs,
1054 tip,
1055 notify_when_messages_are_delivered,
1056 )
1057 .await
1058 }
1059 }
1060 }
1061
1062 async fn preprocess_certified_block(
1065 &mut self,
1066 certificate: ConfirmedBlockCertificate,
1067 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1068 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
1069 let block_hash = certificate.hash();
1070 let block = certificate.block();
1071 let chain_id = block.header.chain_id;
1072 let height = block.header.height;
1073
1074 let tracked = self.reconcile_tracked_outboxes().await?;
1075 let updated_event_streams = self
1076 .chain
1077 .preprocess_block(certificate.value(), tracked.as_deref().map(|h| h.inner()))
1078 .await?;
1079 self.save().await?;
1080 let mut actions = self.create_network_actions(None).await?;
1081 if !updated_event_streams.is_empty() {
1082 actions.notifications.push(Notification {
1083 chain_id,
1084 reason: Reason::NewEvents {
1085 height,
1086 block_hash,
1087 event_streams: updated_event_streams,
1088 },
1089 });
1090 }
1091 trace!("Preprocessed confirmed block {height}");
1092 self.register_delivery_notifier(height, &actions, notify_when_messages_are_delivered)
1093 .await;
1094 Ok((
1095 self.chain_info_response().await?,
1096 actions,
1097 BlockOutcome::Preprocessed,
1098 ))
1099 }
1100
1101 async fn execute_block_with_checkpoint_restore(
1106 &mut self,
1107 certificate: ConfirmedBlockCertificate,
1108 blobs: BTreeMap<BlobId, Blob>,
1109 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1110 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
1111 let (bytes, chain_id, height, previous_block_hash, outbox_block_hashes, inbox_cursors) = {
1112 let block = certificate.block();
1113 let Some(OracleResponse::Checkpoint {
1114 execution_state_blobs,
1115 outbox_block_hashes,
1116 inbox_cursors,
1117 ..
1118 }) = block.body.oracle_responses.first().and_then(|r| r.first())
1119 else {
1120 return Err(ChainError::InternalError(
1121 "Checkpoint block missing OracleResponse::Checkpoint".into(),
1122 )
1123 .into());
1124 };
1125 let mut bytes = Vec::new();
1126 let mut missing = Vec::new();
1127 for hash in execution_state_blobs {
1128 let blob_id = BlobId::new(*hash, BlobType::CheckpointExecutionState);
1129 match blobs.get(&blob_id) {
1130 Some(blob) => bytes.extend_from_slice(blob.bytes()),
1131 None => missing.push(blob_id),
1132 }
1133 }
1134 ensure!(missing.is_empty(), WorkerError::BlobsNotFound(missing));
1135 (
1136 bytes,
1137 block.header.chain_id,
1138 block.header.height,
1139 block.header.previous_block_hash,
1140 outbox_block_hashes.clone(),
1141 inbox_cursors.clone(),
1142 )
1143 };
1144 let mut missing_blocks = Vec::new();
1152 for hash in &outbox_block_hashes {
1153 if !self.storage.contains_certificate(*hash).await? {
1154 missing_blocks.push(*hash);
1155 }
1156 }
1157 if !missing_blocks.is_empty() {
1158 for hash in &missing_blocks {
1159 self.chain.pre_checkpoint_block_trust.insert(hash)?;
1160 }
1161 self.save().await?;
1162 return Err(WorkerError::BlocksNotFound(missing_blocks));
1163 }
1164 self.chain
1165 .execution_state
1166 .restore_from_content(&bytes)
1167 .await?;
1168 self.chain = self.storage.load_chain(chain_id).await?;
1171 let heights = self.chain.collect_unfinalized_heights().await?;
1179 ensure!(
1180 heights.len() == outbox_block_hashes.len(),
1181 ChainError::InternalError(format!(
1182 "checkpoint oracle response has {} outbox block hashes but the \
1183 restored state references {} distinct heights",
1184 outbox_block_hashes.len(),
1185 heights.len(),
1186 ))
1187 );
1188 for (height, hash) in heights.into_iter().zip(outbox_block_hashes) {
1189 self.chain.block_hashes.insert(&height, hash)?;
1190 }
1191 let tracked = self.tracked_full_chains();
1198 self.chain
1199 .restore_outboxes_from_unfinalized(tracked.as_deref())
1200 .await?;
1201 for (origin, cursor) in inbox_cursors {
1202 let mut inbox = self.chain.inboxes.try_load_entry_mut(&origin).await?;
1203 inbox.restore_from_checkpoint(cursor).await?;
1204 }
1205 for (stream_id, count) in self
1212 .chain
1213 .execution_state
1214 .system
1215 .stream_event_counts
1216 .index_values()
1217 .await?
1218 {
1219 self.chain.next_expected_events.insert(
1222 &stream_id,
1223 StreamCounts {
1224 first_index: count,
1225 next_index: count,
1226 },
1227 )?;
1228 }
1229 let new_tip = ChainTipState {
1238 block_hash: previous_block_hash,
1239 next_block_height: height,
1240 };
1241 self.chain.tip_state.set(new_tip.clone());
1242 self.chain
1245 .chain_initialized_at
1246 .set(self.storage.clock().current_time());
1247 self.save().await?;
1248 self.execute_contiguous_block(
1249 certificate,
1250 blobs,
1251 new_tip,
1252 notify_when_messages_are_delivered,
1253 )
1254 .await
1255 }
1256
1257 async fn execute_contiguous_block(
1260 &mut self,
1261 certificate: ConfirmedBlockCertificate,
1262 mut blobs: BTreeMap<BlobId, Blob>,
1263 tip: ChainTipState,
1264 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1265 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
1266 let (cached, plain) = if self.block_export.is_some() {
1269 (Some(self.storage.cache_certificate(certificate)), None)
1270 } else {
1271 (None, Some(certificate))
1272 };
1273 let certificate = cached
1274 .as_deref()
1275 .or(plain.as_ref())
1276 .expect("exactly one of the two is set");
1277 let block_hash = certificate.hash();
1278 let block = certificate.block();
1279 let chain_id = block.header.chain_id;
1280 let height = block.header.height;
1281
1282 ensure!(
1284 tip.block_hash == block.header.previous_block_hash,
1285 WorkerError::InvalidBlockChaining
1286 );
1287
1288 self.initialize_and_save_if_needed().await?;
1291 let (epoch, _) = self.chain.current_committee().await?;
1292 check_block_epoch(epoch, chain_id, block.header.epoch)?;
1293
1294 if certificate.first_round() {
1300 ensure!(
1301 certificate.round() == self.chain.ownership().await?.first_round(),
1302 ChainError::FalseFirstRoundAttestation
1303 );
1304 }
1305
1306 let published_blobs = block
1307 .published_blob_ids()
1308 .iter()
1309 .filter_map(|blob_id| blobs.remove(blob_id))
1310 .collect::<Vec<_>>();
1311
1312 let local_time = self.storage.clock().current_time();
1313 if block.header.timestamp.duration_since(local_time) > self.config.block_time_grace_period {
1314 warn!(
1315 block_timestamp = %block.header.timestamp,
1316 %local_time,
1317 "Confirmed block has a timestamp in the future beyond the block time grace period"
1318 );
1319 }
1320 let tracked = self.reconcile_tracked_outboxes().await?;
1321 let chain = &mut self.chain;
1322 chain
1323 .remove_bundles_from_inboxes(
1324 block.header.timestamp,
1325 false,
1326 block.body.incoming_bundles(),
1327 )
1328 .await?;
1329 let confirmed_block = if let Some(mut execution_state) = self
1330 .execution_state_cache
1331 .as_ref()
1332 .and_then(|cache| cache.remove(&block_hash))
1333 {
1334 chain.execution_state = execution_state
1335 .with_context(|ctx| {
1336 chain
1337 .execution_state
1338 .context()
1339 .clone_with_base_key(ctx.base_key().bytes.clone())
1340 })
1341 .await;
1342 Cow::Borrowed(certificate.value())
1343 } else {
1344 let (proposed_block, outcome) = block.clone().into_proposal();
1345 let (proposed_block, verified, _resource_tracker, _) = chain
1346 .execute_block(
1347 proposed_block,
1348 local_time,
1349 None,
1350 &published_blobs,
1351 BlockExecution::HandleConfirmed {
1352 oracle_responses: outcome.oracle_responses.clone(),
1353 },
1354 )
1355 .await?;
1356 if outcome != verified {
1358 return Err(ChainError::CorruptedChainState(format!(
1359 "computed block outcome differs from the certificate.\n\
1360 Computed: {verified:#?}\n\
1361 Submitted: {outcome:#?}"
1362 ))
1363 .into());
1364 }
1365 Cow::Owned(ConfirmedBlock::new(Block::new(proposed_block, verified)))
1366 };
1367
1368 let updated_streams = chain
1369 .apply_confirmed_block(
1370 &confirmed_block,
1371 local_time,
1372 tracked.as_deref().map(|h| h.inner()),
1373 )
1374 .await?;
1375 self.export_block(cached.as_ref(), published_blobs, blobs)
1376 .await;
1377 let mut actions = self.create_network_actions(None).await?;
1378 trace!("Processed confirmed block {height}");
1379 actions.notifications.push(Notification {
1380 chain_id,
1381 reason: Reason::NewBlock {
1382 height,
1383 hash: block_hash,
1384 },
1385 });
1386 if !updated_streams.is_empty() {
1387 actions.notifications.push(Notification {
1388 chain_id,
1389 reason: Reason::NewEvents {
1390 height,
1391 block_hash,
1392 event_streams: updated_streams,
1393 },
1394 });
1395 }
1396 self.save().await?;
1397
1398 self.block_values.insert_hashed(match confirmed_block {
1399 Cow::Borrowed(block) => Cow::Borrowed(block.inner()),
1400 Cow::Owned(block) => Cow::Owned(block.into_inner()),
1401 });
1402
1403 self.register_delivery_notifier(height, &actions, notify_when_messages_are_delivered)
1404 .await;
1405
1406 Ok((
1407 self.chain_info_response().await?,
1408 actions,
1409 BlockOutcome::Processed,
1410 ))
1411 }
1412
1413 async fn export_block(
1418 &mut self,
1419 certificate: Option<&CacheArc<ConfirmedBlockCertificate>>,
1420 published_blobs: Vec<Blob>,
1421 read_blobs: BTreeMap<BlobId, Blob>,
1422 ) {
1423 let (Some(export), Some(certificate)) = (self.block_export.clone(), certificate) else {
1426 return;
1427 };
1428 let (epoch, committee) = match self.chain.current_committee().await {
1431 Ok((epoch, committee)) => (epoch, committee),
1432 Err(error) => {
1433 warn!(%error, "Not exporting a block of a chain with no current committee");
1434 return;
1435 }
1436 };
1437
1438 let blobs = published_blobs
1441 .into_iter()
1442 .chain(read_blobs.into_values())
1443 .map(|blob| self.storage.cache_blob(blob))
1444 .collect();
1445 export.export(
1446 certificate.clone(),
1447 blobs,
1448 epoch,
1449 (**self.chain.exported_heights.get()).clone(),
1450 );
1451
1452 let acknowledged = export.progress(self.chain.chain_id(), &committee);
1456 let mut merged = std::collections::BTreeMap::new();
1457 for validator in committee.validators().keys() {
1458 let previous = self.chain.exported_heights.get().get(validator).copied();
1459 let reported = acknowledged.get(validator).copied();
1460 if let Some(height) = previous.max(reported) {
1461 merged.insert(*validator, height);
1462 }
1463 }
1464 if **self.chain.exported_heights.get() != merged {
1465 let now = linera_base::time::Instant::now();
1472 let throttled = !self.chain.exported_heights.get().is_empty()
1473 && self.last_exported_heights_fold.is_some_and(|last| {
1474 now.duration_since(last) < self.config.exported_heights_fold_interval
1475 });
1476 if !throttled {
1477 self.last_exported_heights_fold = Some(now);
1478 self.chain.exported_heights.set(merged.into());
1479 }
1480 }
1481 }
1482
1483 #[instrument(level = "trace", skip(self, notify_when_messages_are_delivered))]
1486 async fn register_delivery_notifier(
1487 &self,
1488 height: BlockHeight,
1489 actions: &NetworkActions,
1490 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1491 ) {
1492 if let Some(notifier) = notify_when_messages_are_delivered {
1493 if actions
1494 .cross_chain_requests
1495 .iter()
1496 .any(|request| request.has_messages_lower_or_equal_than(height))
1497 {
1498 self.delivery_notifier.register(height, notifier);
1499 } else {
1500 if let Err(()) = notifier.send(()) {
1503 debug!("Failed to notify message delivery to caller (early case)");
1504 }
1505 }
1506 }
1507 }
1508
1509 #[instrument(level = "debug", skip(self, bundles), fields(chain_id = %self.chain_id()))]
1511 pub(crate) async fn process_cross_chain_update(
1512 &mut self,
1513 origin: ChainId,
1514 bundles: Vec<(Epoch, MessageBundle)>,
1515 sender_previous_height: Option<BlockHeight>,
1516 ) -> Result<CrossChainUpdateResult, WorkerError> {
1517 let mut inbox = self.chain.inboxes.try_load_entry_mut(&origin).await?;
1519 let next_height_to_receive = inbox.next_block_height_to_receive()?;
1520 let last_anticipated_block_height = inbox
1521 .removed_bundles
1522 .back()
1523 .await?
1524 .map(|bundle| bundle.height);
1525
1526 if let Some(prev) = sender_previous_height {
1529 if prev >= next_height_to_receive {
1530 let chain_id = self.chain_id();
1531 if self.config.allow_revert_confirm && self.config.recovery_allowed_for(&chain_id) {
1532 warn!(
1533 %chain_id,
1534 "Inbox gap detected from {origin}: \
1535 sender declares previous height {prev} but we only have up to \
1536 {next_height_to_receive}; requesting resend",
1537 );
1538 return Ok(CrossChainUpdateResult::GapDetected {
1539 origin,
1540 retransmit_from: next_height_to_receive,
1541 });
1542 }
1543 return Err(ChainError::InboxGapDetected {
1544 chain_id,
1545 origin,
1546 expected_height: prev,
1547 actual_height: bundles.first().map(|(_, b)| b.height).unwrap_or_default(),
1548 }
1549 .into());
1550 }
1551 }
1552
1553 let bundles = self
1554 .select_message_bundles(
1555 &origin,
1556 next_height_to_receive,
1557 last_anticipated_block_height,
1558 bundles,
1559 )
1560 .await?;
1561 let Some(last_updated_height) = bundles.last().map(|bundle| bundle.height) else {
1562 return Ok(CrossChainUpdateResult::NothingToDo);
1563 };
1564 let local_time = self.storage.clock().current_time();
1566 let mut previous_height = None;
1567 for bundle in bundles {
1568 let add_to_received_log = previous_height != Some(bundle.height);
1569 previous_height = Some(bundle.height);
1570 self.chain
1572 .receive_message_bundle_with_inbox(
1573 &mut inbox,
1574 &origin,
1575 bundle,
1576 local_time,
1577 add_to_received_log,
1578 )
1579 .await?;
1580 }
1581 inbox.observe_size_metric();
1582 drop(inbox);
1583 if !self.config.allow_inactive_chains && !self.chain.is_active().await? {
1584 warn!(
1588 chain_id = %self.chain_id(),
1589 "Refusing to deliver messages from {origin} \
1590 at height {last_updated_height} because the recipient is still inactive",
1591 );
1592 return Ok(CrossChainUpdateResult::NothingToDo);
1593 }
1594 Ok(CrossChainUpdateResult::Updated(last_updated_height))
1595 }
1596
1597 #[instrument(skip_all, fields(
1599 chain_id = %self.chain_id(),
1600 %recipient,
1601 %latest_height
1602 ))]
1603 pub(crate) async fn confirm_updated_recipient(
1604 &mut self,
1605 recipient: ChainId,
1606 latest_height: BlockHeight,
1607 ) -> Result<bool, WorkerError> {
1608 let tracked = self.reconcile_tracked_outboxes().await?;
1611 Ok(self
1614 .chain
1615 .mark_messages_as_received(
1616 &recipient,
1617 latest_height,
1618 tracked.as_deref().map(|h| h.inner()),
1619 )
1620 .await?
1621 && self.chain.all_messages_delivered_up_to(latest_height))
1622 }
1623
1624 pub(crate) fn notify_delivery(&self, height: BlockHeight) {
1626 self.delivery_notifier.notify(height);
1627 }
1628
1629 pub(crate) async fn process_batch(
1634 &mut self,
1635 requests: Vec<BatchRequest>,
1636 ) -> Result<(), WorkerError> {
1637 let mut update_results = Vec::new();
1638 let mut confirm_results = Vec::new();
1639 let mut need_save = false;
1640 let mut need_rollback = false;
1641 let mut recovery_error = None;
1642 let mut max_delivered_height: Option<BlockHeight> = None;
1643
1644 for request in requests {
1645 match request {
1646 BatchRequest::Update {
1647 origin,
1648 bundles,
1649 previous_height,
1650 result_sender,
1651 } => {
1652 if need_rollback {
1653 send_result(result_sender, Err(WorkerError::BatchRolledBack));
1654 continue;
1655 }
1656 let result = self
1657 .process_cross_chain_update(origin, bundles, previous_height)
1658 .await;
1659 let update_result = match result {
1660 Ok(update_result) => update_result,
1661 Err(error) => {
1662 need_rollback = true;
1663 let (recovery, to_send) = classify_processing_error(error);
1664 recovery_error = recovery_error.or(recovery);
1665 send_result(result_sender, Err(to_send));
1666 continue;
1667 }
1668 };
1669 match &update_result {
1670 CrossChainUpdateResult::Updated(_) => need_save = true,
1671 CrossChainUpdateResult::GapDetected { .. }
1672 | CrossChainUpdateResult::NothingToDo => {}
1673 }
1674 update_results.push((result_sender, update_result));
1675 }
1676 BatchRequest::Confirm {
1677 recipient,
1678 latest_height,
1679 result_sender,
1680 } => {
1681 if need_rollback {
1682 send_result(result_sender, Err(WorkerError::BatchRolledBack));
1683 continue;
1684 }
1685 match self
1686 .confirm_updated_recipient(recipient, latest_height)
1687 .await
1688 {
1689 Ok(fully_delivered) => {
1690 need_save = true;
1691 if fully_delivered {
1692 max_delivered_height = Some(
1693 max_delivered_height
1694 .map_or(latest_height, |h| h.max(latest_height)),
1695 );
1696 }
1697 confirm_results.push((result_sender, recipient));
1698 }
1699 Err(error) => {
1700 need_rollback = true;
1701 let (recovery, to_send) = classify_processing_error(error);
1702 recovery_error = recovery_error.or(recovery);
1703 send_result(result_sender, Err(to_send));
1704 }
1705 }
1706 }
1707 }
1708 }
1709 let mut save_error = None;
1710 if !need_rollback && need_save {
1711 if let Err(error) = self.save().await {
1712 tracing::error!(%error, "failed to save batch; rolling back");
1713 need_rollback = true;
1714 save_error = Some(error);
1715 }
1716 }
1717 if need_rollback {
1718 for (result_sender, _) in update_results {
1719 send_result(result_sender, Err(WorkerError::BatchRolledBack));
1720 }
1721 for (result_sender, _) in confirm_results {
1722 send_result(result_sender, Err(WorkerError::BatchRolledBack));
1723 }
1724 return match save_error.or(recovery_error) {
1734 Some(error) => Err(error),
1735 None => Ok(()),
1736 };
1737 }
1738
1739 if let Some(height) = max_delivered_height {
1740 self.notify_delivery(height);
1741 }
1742
1743 for (result_sender, update_result) in update_results {
1744 send_result(result_sender, Ok(update_result));
1745 }
1746 for (result_sender, recipient) in confirm_results {
1747 let result = self
1748 .create_cross_chain_actions_for_recipient(recipient)
1749 .await;
1750 send_result(result_sender, result);
1751 }
1752 Ok(())
1753 }
1754
1755 #[instrument(skip_all, fields(
1760 chain_id = %self.chain_id(),
1761 %recipient,
1762 %retransmit_from,
1763 ))]
1764 pub(crate) async fn handle_revert_confirm(
1765 &mut self,
1766 recipient: ChainId,
1767 retransmit_from: BlockHeight,
1768 ) -> Result<NetworkActions, WorkerError> {
1769 self.reconcile_tracked_outboxes().await?;
1770 let Some(latest_height) = self
1773 .chain
1774 .execution_state
1775 .previous_message_blocks
1776 .get(&recipient)
1777 .await?
1778 else {
1779 warn!("RevertConfirm: no record of sending to {recipient}");
1780 return Ok(NetworkActions::default());
1781 };
1782
1783 let mut heights_to_re_add = Vec::new();
1784 let mut current_height = latest_height;
1785 while current_height >= retransmit_from {
1786 heights_to_re_add.push(current_height);
1790 let hash = match &*self
1792 .chain
1793 .block_hashes_for_heights([current_height])
1794 .await?
1795 {
1796 [hash] => *hash,
1797 _ => {
1798 return Err(WorkerError::BlockHashNotFound {
1799 height: current_height,
1800 chain_id: self.chain_id(),
1801 })
1802 }
1803 };
1804 let block = self
1805 .read_confirmed_blocks(&[hash])
1806 .await?
1807 .pop()
1808 .flatten()
1809 .ok_or_else(|| WorkerError::LocalBlockNotFound {
1810 height: current_height,
1811 chain_id: self.chain_id(),
1812 })?;
1813 match block.block().body.previous_message_blocks.get(&recipient) {
1814 Some((_, prev_height)) if *prev_height >= retransmit_from => {
1815 current_height = *prev_height;
1816 }
1817 _ => break,
1818 }
1819 }
1820
1821 let new_heights = self
1823 .chain
1824 .outboxes
1825 .try_load_entry_mut(&recipient)
1826 .await?
1827 .revert(&heights_to_re_add)
1828 .await?;
1829
1830 if new_heights.is_empty() {
1831 debug!("RevertConfirm: all heights already in outbox for {recipient}");
1832 return Ok(NetworkActions::default());
1833 }
1834
1835 let new_heights_len = new_heights.len();
1838 if self.is_tracked(&recipient) {
1839 for h in new_heights {
1840 *self.chain.outbox_counters.get_mut().entry(h).or_default() += 1;
1841 }
1842 self.chain.nonempty_outboxes.get_mut().insert(recipient);
1843 }
1844
1845 let actions = self
1847 .create_cross_chain_actions_for_recipient(recipient)
1848 .await?;
1849
1850 self.save().await?;
1852
1853 warn!(
1854 "RevertConfirm: re-added {new_heights_len} heights to outbox for {recipient}, \
1855 starting from height {retransmit_from}"
1856 );
1857
1858 Ok(actions)
1859 }
1860
1861 pub(crate) async fn maybe_reset_corrupted_chain_state(
1865 &mut self,
1866 ) -> Result<Option<Vec<CrossChainRequest>>, WorkerError> {
1867 let Some(min_duration) = self.config.reset_on_corrupted_chain_state else {
1868 return Ok(None);
1869 };
1870 let chain_id = self.chain_id();
1871 if !self.config.recovery_allowed_for(&chain_id) {
1872 return Ok(None);
1873 }
1874 let local_time = self.storage.clock().current_time();
1875 let initialized_time = *self.chain.chain_initialized_at.get();
1876 let elapsed = local_time.duration_since(initialized_time);
1877 if elapsed < min_duration {
1878 warn!(
1879 %chain_id, ?elapsed, ?min_duration,
1880 "Not resetting corrupted chain state; not enough time elapsed \
1881 since the chain was last initialized"
1882 );
1883 return Ok(None);
1884 }
1885 warn!(%chain_id, "Corrupted chain state detected; resetting and re-executing");
1886 Ok(Some(self.reset_and_reexecute_chain().await?))
1887 }
1888
1889 #[instrument(skip_all, fields(
1893 chain_id = %self.chain_id(),
1894 ))]
1895 pub(crate) async fn reset_and_reexecute_chain(
1896 &mut self,
1897 ) -> Result<Vec<CrossChainRequest>, WorkerError> {
1898 let chain_id = self.chain_id();
1899 let tip_height = self.chain.tip_state.get().next_block_height;
1900
1901 let sender_ids = self.chain.inboxes.indices().await?;
1903 let block_hashes = self.chain.block_hashes.index_values().await?;
1904 let restore_from =
1909 (*self.chain.latest_checkpoint_height.get()).unwrap_or(BlockHeight::ZERO);
1910
1911 let manager_snapshot = ManagerSafetySnapshot::capture(&self.chain.manager).await?;
1914
1915 self.wipe_and_reload_chain().await?;
1924 self.knows_chain_is_active = false;
1925 warn!(
1926 %chain_id,
1927 "Cleared chain state up to height {tip_height}; \
1928 re-executing blocks from height {restore_from}"
1929 );
1930
1931 let total = block_hashes
1936 .iter()
1937 .filter(|(height, _)| *height >= restore_from)
1938 .count();
1939 let mut replayed = 0;
1940 for (height, hash) in block_hashes {
1941 if height < restore_from {
1942 continue;
1943 }
1944 if replayed % 1000 == 0 {
1945 info!(
1946 %chain_id, replayed, total,
1947 "Re-executing confirmed blocks after reset"
1948 );
1949 }
1950 replayed += 1;
1951 let cert = self
1952 .storage
1953 .read_certificate(hash)
1954 .await?
1955 .map(CacheArc::unwrap_or_clone)
1956 .ok_or_else(|| WorkerError::LocalBlockNotFound { height, chain_id })?;
1957 Box::pin(self.process_confirmed_block(cert, ProcessConfirmedBlockMode::Execute, None))
1958 .await?;
1959 }
1960
1961 let new_tip_height = self.chain.tip_state.get().next_block_height;
1969 if new_tip_height == tip_height {
1970 manager_snapshot.restore(&mut self.chain.manager)?;
1971 self.save().await?;
1972 } else {
1973 warn!(
1974 %tip_height, %new_tip_height,
1975 "Dropping manager snapshot: pre-reset tip differs from post-reset tip"
1976 );
1977 }
1978
1979 let revert_requests = sender_ids
1982 .into_iter()
1983 .map(|sender| CrossChainRequest::RevertConfirm {
1984 sender,
1985 recipient: chain_id,
1986 retransmit_from: BlockHeight::ZERO,
1987 })
1988 .collect::<Vec<_>>();
1989
1990 warn!(
1991 tip_height = %self.chain.tip_state.get().next_block_height,
1992 num_revert_confirms = revert_requests.len(),
1993 "Chain reset and re-executed; sending RevertConfirm to senders"
1994 );
1995
1996 Ok(revert_requests)
1997 }
1998
1999 #[instrument(skip_all, fields(
2000 chain_id = %self.chain_id(),
2001 num_trackers = %new_trackers.len()
2002 ))]
2003 pub(crate) async fn update_received_certificate_trackers(
2004 &mut self,
2005 new_trackers: BTreeMap<ValidatorPublicKey, u64>,
2006 ) -> Result<(), WorkerError> {
2007 self.chain
2008 .update_received_certificate_trackers(new_trackers);
2009 self.save().await?;
2010 Ok(())
2011 }
2012
2013 #[instrument(skip_all, fields(
2015 chain_id = %self.chain_id(),
2016 start = %start,
2017 end = %end
2018 ))]
2019 pub(crate) async fn get_preprocessed_block_hashes(
2020 &self,
2021 start: BlockHeight,
2022 end: BlockHeight,
2023 ) -> Result<Vec<CryptoHash>, WorkerError> {
2024 let mut hashes = Vec::new();
2025 let mut height = start;
2026 while height < end {
2027 match self.chain.block_hashes.get(&height).await? {
2028 Some(hash) => hashes.push(hash),
2029 None => break,
2030 }
2031 height = height.try_add_one()?;
2032 }
2033 Ok(hashes)
2034 }
2035
2036 #[instrument(skip_all, fields(
2038 chain_id = %self.chain_id(),
2039 origin = %origin
2040 ))]
2041 pub(crate) async fn get_inbox_next_height(
2042 &self,
2043 origin: ChainId,
2044 ) -> Result<BlockHeight, WorkerError> {
2045 Ok(match self.chain.inboxes.try_load_entry(&origin).await? {
2046 Some(inbox) => inbox.next_block_height_to_receive()?,
2047 None => BlockHeight::ZERO,
2048 })
2049 }
2050
2051 #[instrument(skip_all, fields(
2054 chain_id = %self.chain_id(),
2055 num_blob_ids = %blob_ids.len()
2056 ))]
2057 pub(crate) async fn get_locking_blobs(
2058 &self,
2059 blob_ids: Vec<BlobId>,
2060 ) -> Result<Option<Vec<Blob>>, WorkerError> {
2061 let results = self
2062 .chain
2063 .manager
2064 .locking_blobs
2065 .multi_get(&blob_ids)
2066 .await?;
2067 Ok(results.into_iter().collect())
2068 }
2069
2070 pub(crate) async fn get_block_hashes(
2072 &self,
2073 heights: Vec<BlockHeight>,
2074 ) -> Result<Vec<CryptoHash>, WorkerError> {
2075 Ok(self.chain.block_hashes_for_heights(heights).await?)
2076 }
2077
2078 pub(crate) async fn get_proposed_blobs(
2080 &self,
2081 blob_ids: Vec<BlobId>,
2082 ) -> Result<Vec<Blob>, WorkerError> {
2083 let results = self
2084 .chain
2085 .manager
2086 .proposed_blobs
2087 .multi_get(&blob_ids)
2088 .await?;
2089 let mut blobs = Vec::with_capacity(blob_ids.len());
2090 let mut missing = Vec::new();
2091 for (blob_id, maybe_blob) in blob_ids.into_iter().zip(results) {
2092 match maybe_blob {
2093 Some(blob) => blobs.push(blob),
2094 None => missing.push(blob_id),
2095 }
2096 }
2097 if !missing.is_empty() {
2098 return Err(WorkerError::BlobsNotFound(missing));
2099 }
2100 Ok(blobs)
2101 }
2102
2103 pub(crate) async fn get_event_subscriptions(
2105 &self,
2106 ) -> Result<EventSubscriptionsResult, WorkerError> {
2107 Ok(self
2108 .chain
2109 .execution_state
2110 .system
2111 .event_subscriptions
2112 .index_values()
2113 .await?)
2114 }
2115
2116 pub(crate) async fn get_stream_indices(
2122 &self,
2123 stream_id: StreamId,
2124 ) -> Result<StreamCounts, WorkerError> {
2125 Ok(self
2126 .chain
2127 .next_expected_events
2128 .get(&stream_id)
2129 .await?
2130 .unwrap_or_default())
2131 }
2132
2133 pub(crate) async fn get_next_expected_events(
2135 &self,
2136 stream_ids: Vec<StreamId>,
2137 ) -> Result<BTreeMap<StreamId, u32>, WorkerError> {
2138 let values = self
2139 .chain
2140 .next_expected_events
2141 .multi_get(&stream_ids)
2142 .await?;
2143 Ok(stream_ids
2144 .into_iter()
2145 .zip(values)
2146 .filter_map(|(id, val)| Some((id, val?.next_index)))
2147 .collect())
2148 }
2149
2150 pub(crate) async fn get_received_certificate_trackers(
2152 &self,
2153 ) -> Result<HashMap<ValidatorPublicKey, u64>, WorkerError> {
2154 Ok(self.chain.received_certificate_trackers.get().clone())
2155 }
2156
2157 pub(crate) async fn get_tip_state_and_outbox_info(
2159 &self,
2160 receiver_id: ChainId,
2161 ) -> Result<(BlockHeight, Option<BlockHeight>), WorkerError> {
2162 let next_block_height = self.chain.tip_state.get().next_block_height;
2163 let next_height_to_schedule = self
2164 .chain
2165 .outboxes
2166 .try_load_entry(&receiver_id)
2167 .await?
2168 .map(|outbox| *outbox.next_height_to_schedule.get());
2169 Ok((next_block_height, next_height_to_schedule))
2170 }
2171
2172 pub(crate) fn get_next_height_to_preprocess(&self) -> BlockHeight {
2174 *self.chain.next_height_to_preprocess.get()
2175 }
2176
2177 #[instrument(skip_all, fields(
2179 chain_id = %self.chain_id(),
2180 height = %height,
2181 round = %round
2182 ))]
2183 async fn vote_for_leader_timeout(
2184 &mut self,
2185 height: BlockHeight,
2186 round: Round,
2187 ) -> Result<(), WorkerError> {
2188 let chain = &mut self.chain;
2189 ensure!(
2190 height == chain.tip_state.get().next_block_height,
2191 WorkerError::UnexpectedBlockHeight {
2192 expected_block_height: chain.tip_state.get().next_block_height,
2193 found_block_height: height
2194 }
2195 );
2196 let epoch = chain.execution_state.system.epoch.get();
2197 let chain_id = chain.chain_id();
2198 let key_pair = self.config.key_pair();
2199 let local_time = self.storage.clock().current_time();
2200 if chain
2201 .manager
2202 .create_timeout_vote(chain_id, height, round, *epoch, key_pair, local_time)?
2203 {
2204 self.save().await?;
2205 }
2206 Ok(())
2207 }
2208
2209 #[instrument(skip_all, fields(
2214 chain_id = %self.chain_id()
2215 ))]
2216 async fn vote_for_fallback(&mut self) -> Result<(), WorkerError> {
2217 let chain = &mut self.chain;
2218 let epoch = *chain.execution_state.system.epoch.get();
2219 let Some(admin_chain_id) = chain.execution_state.system.admin_chain_id.get() else {
2220 return Ok(());
2221 };
2222
2223 let next_epoch_index = epoch.0.saturating_add(1);
2225 let event_id = EventId {
2226 chain_id: *admin_chain_id,
2227 stream_id: StreamId::system(EPOCH_STREAM_NAME),
2228 index: next_epoch_index,
2229 };
2230
2231 let Some(event_bytes) = self.storage.read_event(event_id).await? else {
2232 return Ok(()); };
2234
2235 let event_data: EpochEventData = bcs::from_bytes(&event_bytes)?;
2236 let elapsed = self
2237 .storage
2238 .clock()
2239 .current_time()
2240 .delta_since(event_data.timestamp);
2241 if elapsed >= chain.ownership().await?.timeout_config.fallback_duration {
2242 let chain_id = chain.chain_id();
2243 let height = chain.tip_state.get().next_block_height;
2244 let key_pair = self.config.key_pair();
2245 if chain
2246 .manager
2247 .vote_fallback(chain_id, height, epoch, key_pair)
2248 {
2249 self.save().await?;
2250 }
2251 }
2252 Ok(())
2253 }
2254
2255 #[instrument(skip_all, fields(
2256 chain_id = %self.chain_id(),
2257 blob_id = %blob.id()
2258 ))]
2259 pub(crate) async fn handle_pending_blob(
2260 &mut self,
2261 blob: Blob,
2262 ) -> Result<ChainInfoResponse, WorkerError> {
2263 let mut was_expected = self
2264 .chain
2265 .pending_validated_blobs
2266 .maybe_insert(&blob)
2267 .await?;
2268 for (_, mut pending_blobs) in self
2269 .chain
2270 .pending_proposed_blobs
2271 .try_load_all_entries_mut()
2272 .await?
2273 {
2274 if !pending_blobs.validated.get() {
2275 let (_, committee) = self.chain.current_committee().await?;
2276 let policy = committee.policy();
2277 policy
2278 .check_blob_size(blob.content())
2279 .with_execution_context(ChainExecutionContext::Block)?;
2280 ensure!(
2281 u64::try_from(pending_blobs.pending_blobs.iterative_count().await?)
2282 .is_ok_and(|count| count < policy.maximum_published_blobs),
2283 WorkerError::TooManyPublishedBlobs(policy.maximum_published_blobs)
2284 );
2285 }
2286 was_expected = was_expected || pending_blobs.maybe_insert(&blob).await?;
2287 }
2288 ensure!(was_expected, WorkerError::UnexpectedBlob);
2289 self.save().await?;
2290 self.chain_info_response().await
2291 }
2292
2293 #[cfg(with_testing)]
2298 #[instrument(skip_all, fields(
2299 chain_id = %self.chain_id(),
2300 height = %height
2301 ))]
2302 pub(crate) async fn read_certificate(
2303 &self,
2304 height: BlockHeight,
2305 ) -> Result<Option<CacheArc<ConfirmedBlockCertificate>>, WorkerError> {
2306 let certificate_hash = match self.chain.block_hashes.get(&height).await? {
2307 Some(hash) => hash,
2308 None => return Ok(None),
2309 };
2310 let certificate = self
2311 .storage
2312 .read_certificate(certificate_hash)
2313 .await?
2314 .ok_or(WorkerError::BlocksNotFound(vec![certificate_hash]))?;
2315 Ok(Some(certificate))
2316 }
2317
2318 #[instrument(skip_all, fields(
2320 chain_id = %self.chain_id(),
2321 query_application_id = %query.application_id()
2322 ))]
2323 pub(crate) async fn query_application(
2324 &mut self,
2325 query: Query,
2326 block_hash: Option<CryptoHash>,
2327 ) -> Result<(QueryOutcome, BlockHeight), WorkerError> {
2328 self.initialize_and_save_if_needed().await?;
2329 let next_block_height = self.chain.tip_state.get().next_block_height;
2330 let local_time = self.storage.clock().current_time();
2331 let cached_state = block_hash
2334 .zip(self.execution_state_cache.as_ref())
2335 .and_then(|(h, cache)| Some(h).zip(cache.remove(&h)));
2336 if let Some((requested_block, mut state)) = cached_state {
2337 let next_block_height = next_block_height
2338 .try_add_one()
2339 .expect("block height to not overflow");
2340 let context = QueryContext {
2341 chain_id: self.chain_id(),
2342 next_block_height,
2343 local_time,
2344 };
2345 let outcome = state
2346 .with_context(|ctx| {
2347 self.chain
2348 .execution_state
2349 .context()
2350 .clone_with_base_key(ctx.base_key().bytes.clone())
2351 })
2352 .await
2353 .query_application(context, query, self.service_runtime_endpoint.as_mut())
2354 .await
2355 .with_execution_context(ChainExecutionContext::Query)?;
2356 if let Some(cache) = &self.execution_state_cache {
2357 cache.insert(&requested_block, state);
2358 }
2359 Ok((outcome, next_block_height))
2360 } else {
2361 if block_hash.is_some() {
2362 tracing::debug!(
2363 "requested block hash not found in cache, querying committed state"
2364 );
2365 }
2366 let outcome = self
2367 .chain
2368 .query_application(local_time, query, self.service_runtime_endpoint.as_mut())
2369 .await?;
2370 Ok((outcome, next_block_height))
2371 }
2372 }
2373
2374 #[instrument(skip_all, fields(
2380 chain_id = %self.chain_id(),
2381 application_id = %application_id
2382 ))]
2383 pub(crate) async fn describe_application_readonly(
2384 &self,
2385 application_id: ApplicationId,
2386 ) -> Result<ApplicationDescription, WorkerError> {
2387 let blob_id = application_id.description_blob_id();
2388 let blob = self
2389 .storage
2390 .read_blob(blob_id)
2391 .await?
2392 .ok_or(WorkerError::BlobsNotFound(vec![blob_id]))?;
2393 Ok(bcs::from_bytes(blob.bytes())?)
2394 }
2395
2396 #[instrument(skip_all, fields(
2402 chain_id = %self.chain_id(),
2403 block_height = %block.height
2404 ))]
2405 pub(crate) async fn stage_block_execution(
2406 &mut self,
2407 block: ProposedBlock,
2408 round: Option<u32>,
2409 published_blobs: &[Blob],
2410 policy: BundleExecutionPolicy,
2411 ) -> Result<
2412 (
2413 ProposedBlock,
2414 Block,
2415 ChainInfoResponse,
2416 ResourceTracker,
2417 HashSet<ChainId>,
2418 ),
2419 WorkerError,
2420 > {
2421 self.initialize_and_save_if_needed().await?;
2422 let local_time = self.storage.clock().current_time();
2423 let (_, committee) = self.chain.current_committee().await?;
2424 block.check_proposal_size(committee.policy().maximum_block_proposal_size)?;
2425
2426 self.chain
2427 .remove_bundles_from_inboxes(block.timestamp, true, block.incoming_bundles())
2428 .await?;
2429 let (executed_block, resource_tracker, never_reject_origins) =
2430 Box::pin(self.execute_block(
2431 block,
2432 local_time,
2433 round,
2434 published_blobs,
2435 BlockExecution::StageProposal { policy },
2436 ))
2437 .await?;
2438
2439 let info = ChainInfo::from_chain_view(&mut self.chain).await?;
2441 let mut response = ChainInfoResponse::new(info, None);
2442 if let Some(owner) = executed_block.header.authenticated_owner {
2443 response.info.requested_owner_balance = self
2444 .chain
2445 .execution_state
2446 .system
2447 .balances
2448 .get(&owner)
2449 .await?;
2450 }
2451
2452 let (proposed_block, _) = executed_block.clone().into_proposal();
2453 Ok((
2454 proposed_block,
2455 executed_block,
2456 response,
2457 resource_tracker,
2458 never_reject_origins,
2459 ))
2460 }
2461
2462 #[instrument(skip_all, fields(
2469 chain_id = %self.chain_id(),
2470 block_height = %proposal.content.block.height
2471 ))]
2472 pub(crate) async fn handle_block_proposal(
2473 &mut self,
2474 proposal: BlockProposal,
2475 ) -> (Result<ChainInfoResponse, WorkerError>, NetworkActions) {
2476 #[cfg(with_metrics)]
2477 metrics::BLOCK_PROPOSALS_RECEIVED_TOTAL.inc();
2478 let chain_id = proposal.content.block.chain_id;
2479 let height = proposal.content.block.height;
2480 let old_round = self.chain.manager.current_round();
2481 match self.try_handle_block_proposal(proposal).await {
2482 Ok((response, actions)) => (Ok(response), actions),
2483 Err(err) => {
2484 let error_type = err.error_type();
2485 #[cfg(with_metrics)]
2486 metrics::BLOCK_PROPOSALS_REJECTED_TOTAL
2487 .with_label_values(&[error_type.as_str()])
2488 .inc();
2489 debug!(%chain_id, %height, %error_type, "Block proposal rejected");
2490 let actions = if self.chain.manager.current_round() != old_round {
2495 self.create_network_actions(Some(old_round))
2496 .await
2497 .unwrap_or_default()
2498 } else {
2499 NetworkActions::default()
2500 };
2501 (Err(err), actions)
2502 }
2503 }
2504 }
2505
2506 async fn try_handle_block_proposal(
2507 &mut self,
2508 proposal: BlockProposal,
2509 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
2510 self.initialize_and_save_if_needed().await?;
2511 proposal
2512 .check_invariants()
2513 .map_err(|msg| WorkerError::InvalidBlockProposal(msg.to_string()))?;
2514 proposal.check_signature()?;
2515 let owner = proposal.owner();
2516 let BlockProposal {
2517 content,
2518 original_proposal,
2519 signature: _,
2520 } = &proposal;
2521 let block = &content.block;
2522 let chain = &self.chain;
2523 chain.tip_state.get().verify_block_chaining(block)?;
2525 let (epoch, committee) = chain.current_committee().await?;
2527 check_block_epoch(epoch, block.chain_id, block.epoch)?;
2528 let policy = committee.policy().clone();
2529 block.check_proposal_size(policy.maximum_block_proposal_size)?;
2530 ensure!(
2532 chain.manager.can_propose(&owner, proposal.content.round),
2533 WorkerError::InvalidOwner
2534 );
2535 let old_round = self.chain.manager.current_round();
2536 match original_proposal {
2537 None => {
2538 if let Some(signer) = block.authenticated_owner {
2539 ensure!(signer == owner, WorkerError::InvalidSigner(owner));
2541 }
2542 }
2543 Some(OriginalProposal::Regular { certificate }) => {
2544 certificate.check(&committee)?;
2546 }
2547 Some(OriginalProposal::Fast(signature)) => {
2548 let original_proposal = BlockProposal {
2549 content: ProposalContent {
2550 block: content.block.clone(),
2551 round: Round::Fast,
2552 outcome: None,
2553 },
2554 signature: *signature,
2555 original_proposal: None,
2556 };
2557 let super_owner = original_proposal.owner();
2558 ensure!(
2559 chain
2560 .manager
2561 .ownership
2562 .get()
2563 .super_owners
2564 .contains(&super_owner),
2565 WorkerError::InvalidOwner
2566 );
2567 if let Some(signer) = block.authenticated_owner {
2568 ensure!(signer == super_owner, WorkerError::InvalidSigner(signer));
2570 }
2571 original_proposal.check_signature()?;
2572 }
2573 }
2574 let local_time = self.storage.clock().current_time();
2575 match chain.manager.check_proposed_block(&proposal) {
2576 Ok(manager::Outcome::Skip) => {
2577 return Ok((self.chain_info_response().await?, NetworkActions::default()));
2579 }
2580 Ok(manager::Outcome::Accept) => {}
2581 Err(err) => {
2582 if matches!(err, ChainError::HasIncompatibleConfirmedVote(_, _))
2590 && self
2591 .chain
2592 .manager
2593 .update_signed_proposal(&proposal, local_time)
2594 {
2595 self.save().await?;
2596 }
2597 return Err(err.into());
2598 }
2599 }
2600
2601 if self
2604 .chain
2605 .manager
2606 .update_signed_proposal(&proposal, local_time)
2607 {
2608 self.save().await?;
2609 }
2610
2611 let published_blobs = self.load_proposal_blobs(&proposal).await?;
2612 let ProposalContent {
2613 block,
2614 round,
2615 outcome,
2616 } = content;
2617
2618 if self.config.key_pair().is_some()
2619 && block.timestamp.duration_since(local_time) > self.config.block_time_grace_period
2620 {
2621 return Err(WorkerError::InvalidTimestamp {
2622 local_time,
2623 block_timestamp: block.timestamp,
2624 block_time_grace_period: self.config.block_time_grace_period,
2625 });
2626 }
2627 self.chain
2632 .remove_bundles_from_inboxes(block.timestamp, true, block.incoming_bundles())
2633 .await?;
2634 let block = if let Some(outcome) = outcome {
2635 outcome.clone().with(proposal.content.block.clone())
2636 } else {
2637 let (executed_block, _resource_tracker, _) = Box::pin(self.execute_block(
2638 block.clone(),
2639 local_time,
2640 round.multi_leader(),
2641 &published_blobs,
2642 BlockExecution::HandleProposal,
2643 ))
2644 .await?;
2645 executed_block
2646 };
2647
2648 ensure!(
2649 !round.is_fast() || !block.has_oracle_responses(),
2650 WorkerError::FastBlockUsingOracles
2651 );
2652 let chain = &mut self.chain;
2653 chain.rollback();
2655
2656 let blobs = self
2658 .get_required_blobs(proposal.expected_blob_ids(), block.created_blobs())
2659 .await?;
2660 let key_pair = self.config.key_pair();
2661 let manager = &mut self.chain.manager;
2662 match manager.create_vote(&proposal, block, key_pair, local_time, blobs)? {
2663 Some(Either::Left(vote)) => {
2665 self.block_values
2666 .insert_hashed(Cow::Borrowed(vote.value.inner()));
2667 }
2668 Some(Either::Right(vote)) => {
2669 self.block_values
2670 .insert_hashed(Cow::Borrowed(vote.value.inner()));
2671 }
2672 None => (),
2673 }
2674 self.save().await?;
2675 let actions = self.create_network_actions(Some(old_round)).await?;
2676 Ok((self.chain_info_response().await?, actions))
2677 }
2678
2679 #[instrument(skip_all, fields(
2681 chain_id = %self.chain_id()
2682 ))]
2683 async fn prepare_chain_info_response(
2684 &mut self,
2685 query: ChainInfoQuery,
2686 ) -> Result<ChainInfoResponse, WorkerError> {
2687 self.initialize_and_save_if_needed().await?;
2688 let mut info = ChainInfo::from_chain_view(&mut self.chain).await?;
2689 let chain = &self.chain;
2690 if query.request_owner_balance == AccountOwner::CHAIN {
2691 info.requested_owner_balance = Some(*chain.execution_state.system.balance.get());
2692 } else {
2693 info.requested_owner_balance = chain
2694 .execution_state
2695 .system
2696 .balances
2697 .get(&query.request_owner_balance)
2698 .await?;
2699 }
2700 if let Some(next_block_height) = query.test_next_block_height {
2701 ensure!(
2703 chain.tip_state.get().next_block_height == next_block_height,
2704 WorkerError::UnexpectedBlockHeight {
2705 expected_block_height: chain.tip_state.get().next_block_height,
2706 found_block_height: next_block_height,
2707 }
2708 );
2709 }
2710 if query.request_pending_message_bundles {
2711 let mut bundles = Vec::new();
2712 let nonempty_origins: Vec<ChainId> =
2713 chain.nonempty_inboxes.get().iter().copied().collect();
2714 #[cfg(with_metrics)]
2715 metrics::NUM_INBOXES
2716 .with_label_values(&[])
2717 .observe(nonempty_origins.len() as f64);
2718 let is_closed = *chain.execution_state.system.closed.get();
2719 let action = if is_closed {
2720 MessageAction::Reject
2721 } else {
2722 MessageAction::Accept
2723 };
2724 let inboxes = chain.inboxes.try_load_entries(&nonempty_origins).await?;
2725 for (origin, inbox) in nonempty_origins.into_iter().zip(inboxes) {
2726 let inbox = inbox.ok_or_else(|| {
2727 ChainError::InternalError(format!("Missing inbox for origin {origin}"))
2728 })?;
2729 for bundle in inbox.added_bundles.elements().await? {
2730 bundles.push(IncomingBundle {
2731 origin,
2732 bundle,
2733 action,
2734 });
2735 }
2736 }
2737 if is_closed && !bundles.is_empty() {
2738 info!(
2739 chain_id = %chain.chain_id(),
2740 count = bundles.len(),
2741 "Auto-rejecting all incoming message bundles because the chain is closed"
2742 );
2743 }
2744 info.requested_pending_message_bundles = bundles;
2745 }
2746 let hashes = chain
2747 .block_hashes_for_heights(query.request_sent_certificate_hashes_by_heights)
2748 .await?;
2749 info.requested_sent_certificate_hashes = hashes;
2750 if let Some(start) = query.request_received_log_excluding_first_n {
2751 let start = usize::try_from(start).map_err(|_| ArithmeticError::Overflow)?;
2752 let max_received_log_entries = self.config.chain_info_max_received_log_entries;
2753 let end = start
2754 .saturating_add(max_received_log_entries)
2755 .min(chain.received_log.count());
2756 info.requested_received_log = chain.received_log.read(start..end).await?;
2757 #[cfg(with_metrics)]
2758 metrics::RECEIVED_LOG_QUERY_ENTRIES.observe(info.requested_received_log.len() as f64);
2759 }
2760 if query.request_manager_values {
2761 info.manager.add_values(&chain.manager);
2762 }
2763 if !query.request_previous_event_blocks.is_empty() {
2764 let stream_ids = query.request_previous_event_blocks;
2765 let heights = chain
2766 .execution_state
2767 .previous_event_blocks
2768 .multi_get(&stream_ids)
2769 .await?;
2770 let mut streams_with_heights = Vec::new();
2771 for (stream_id, height) in stream_ids.into_iter().zip(heights) {
2772 if let Some(height) = height {
2773 streams_with_heights.push((stream_id, height));
2774 }
2775 }
2776 let hashes = chain
2777 .block_hashes
2778 .multi_get(streams_with_heights.iter().map(|(_, height)| height))
2779 .await?;
2780 for (maybe_hash, (stream_id, height)) in hashes.into_iter().zip(streams_with_heights) {
2781 let hash = maybe_hash.ok_or_else(|| WorkerError::BlockHashNotFound {
2782 height,
2783 chain_id: info.chain_id,
2784 })?;
2785 info.requested_previous_event_blocks
2786 .insert(stream_id, (height, hash));
2787 }
2788 }
2789 if query.request_latest_checkpoint_height {
2790 info.requested_latest_checkpoint_height = *self.chain.latest_checkpoint_height.get();
2791 }
2792 Ok(ChainInfoResponse::new(info, self.config.key_pair()))
2793 }
2794
2795 #[instrument(skip_all, fields(
2799 chain_id = %self.chain_id(),
2800 block_height = %block.height
2801 ))]
2802 async fn execute_block(
2803 &mut self,
2804 block: ProposedBlock,
2805 local_time: Timestamp,
2806 round: Option<u32>,
2807 published_blobs: &[Blob],
2808 execution: BlockExecution,
2809 ) -> Result<(Block, ResourceTracker, HashSet<ChainId>), WorkerError> {
2810 let (proposed_block, outcome, resource_tracker, never_reject_origins) = Box::pin(
2811 self.chain
2812 .execute_block(block, local_time, round, published_blobs, execution),
2813 )
2814 .await?;
2815 let executed_block = Block::new(proposed_block, outcome);
2816 let block_hash = executed_block.hash();
2817 if let Some(cache) = &self.execution_state_cache {
2818 cache.insert(
2819 &block_hash,
2820 Box::pin(
2821 self.chain
2822 .execution_state
2823 .with_context(|ctx| InactiveContext(ctx.base_key().clone())),
2824 )
2825 .await,
2826 );
2827 }
2828 Ok((executed_block, resource_tracker, never_reject_origins))
2829 }
2830
2831 #[instrument(skip_all, fields(
2833 chain_id = %self.chain_id()
2834 ))]
2835 pub(crate) async fn initialize_and_save_if_needed(&mut self) -> Result<(), WorkerError> {
2836 if !self.knows_chain_is_active {
2837 let local_time = self.storage.clock().current_time();
2838 self.chain.initialize_if_needed(local_time).await?;
2839 self.save().await?;
2840 self.knows_chain_is_active = true;
2841 }
2842 Ok(())
2843 }
2844
2845 pub(crate) async fn chain_info_response(&mut self) -> Result<ChainInfoResponse, WorkerError> {
2846 let info = ChainInfo::from_chain_view(&mut self.chain).await?;
2847 Ok(ChainInfoResponse::new(info, self.config.key_pair()))
2848 }
2849
2850 #[instrument(skip_all, fields(
2854 chain_id = %self.chain_id()
2855 ))]
2856 pub(crate) async fn save(&mut self) -> Result<(), WorkerError> {
2857 if let Err(error) = self.chain.save().await {
2858 if error.must_reload_view() {
2859 tracing::error!(
2860 ?error,
2861 chain_id = %self.chain_id(),
2862 "Chain save failed with a nonrecoverable error; marking worker as poisoned"
2863 );
2864 self.poisoned = true;
2865 }
2866 return Err(WorkerError::ViewError(error));
2867 }
2868 Ok(())
2869 }
2870
2871 #[instrument(skip_all, fields(
2876 chain_id = %self.chain_id()
2877 ))]
2878 async fn wipe_and_reload_chain(&mut self) -> Result<(), WorkerError> {
2879 let context = self.chain.context().clone();
2880 let mut batch = Batch::new();
2881 batch.delete_key_prefix(Vec::new());
2882 if let Err(error) = context.store().write_batch(batch).await {
2883 tracing::error!(
2884 ?error,
2885 chain_id = %self.chain_id(),
2886 "Wiping chain storage failed; marking worker as poisoned"
2887 );
2888 self.poisoned = true;
2889 return Err(WorkerError::PoisonedWorker);
2890 }
2891 match ChainStateView::load(context).await {
2892 Ok(chain) => {
2893 self.chain = chain;
2894 Ok(())
2895 }
2896 Err(error) => {
2897 tracing::error!(
2898 ?error,
2899 chain_id = %self.chain_id(),
2900 "Reloading chain after wipe failed; marking worker as poisoned"
2901 );
2902 self.poisoned = true;
2903 Err(WorkerError::PoisonedWorker)
2904 }
2905 }
2906 }
2907}
2908
2909fn classify_processing_error(error: WorkerError) -> (Option<WorkerError>, WorkerError) {
2917 if error.must_reload_view() || error.indicates_corrupted_chain_state() {
2918 (Some(error), WorkerError::BatchRolledBack)
2919 } else {
2920 (None, error)
2921 }
2922}
2923
2924pub(crate) fn send_result<T>(sender: oneshot::Sender<T>, value: T) {
2927 if sender.send(value).is_err() {
2928 tracing::debug!("cannot send cross-chain result; receiver dropped");
2929 }
2930}
2931
2932fn missing_indices_blob_ids(maybe_blobs: &[(BlobId, Option<Blob>)]) -> (Vec<usize>, Vec<BlobId>) {
2934 let mut missing_indices = Vec::new();
2935 let mut missing_blob_ids = Vec::new();
2936 for (index, (blob_id, blob)) in maybe_blobs.iter().enumerate() {
2937 if blob.is_none() {
2938 missing_indices.push(index);
2939 missing_blob_ids.push(*blob_id);
2940 }
2941 }
2942 (missing_indices, missing_blob_ids)
2943}
2944
2945fn missing_blob_ids<'a>(
2947 maybe_blobs: impl IntoIterator<Item = (&'a BlobId, &'a Option<Blob>)>,
2948) -> Vec<BlobId> {
2949 maybe_blobs
2950 .into_iter()
2951 .filter(|(_, maybe_blob)| maybe_blob.is_none())
2952 .map(|(blob_id, _)| *blob_id)
2953 .collect()
2954}
2955
2956fn check_block_epoch(
2958 chain_epoch: Epoch,
2959 block_chain: ChainId,
2960 block_epoch: Epoch,
2961) -> Result<(), WorkerError> {
2962 ensure!(
2963 block_epoch == chain_epoch,
2964 WorkerError::InvalidEpoch {
2965 chain_id: block_chain,
2966 epoch: block_epoch,
2967 chain_epoch
2968 }
2969 );
2970 Ok(())
2971}