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, Round, Timestamp,
19 },
20 ensure,
21 hashed::Hashed,
22 identifiers::{AccountOwner, ApplicationId, BlobId, ChainId, StreamId},
23};
24use linera_cache::{Arc as CacheArc, UniqueValueCache, ValueCache};
25use linera_chain::{
26 data_types::{
27 BlockProposal, BundleExecutionPolicy, IncomingBundle, MessageAction, MessageBundle,
28 OriginalProposal, ProposalContent, ProposedBlock,
29 },
30 manager::{self, ManagerSafetySnapshot},
31 types::{
32 Block, ConfirmedBlock, ConfirmedBlockCertificate, TimeoutCertificate,
33 ValidatedBlockCertificate,
34 },
35 BlockExecution, ChainError, ChainExecutionContext, ChainIdSet, ChainStateView, ChainTipState,
36 ExecutionResultExt as _,
37};
38use linera_execution::{
39 system::EventSubscriptions, ExecutionRuntimeContext as _, ExecutionStateView, Query,
40 QueryContext, QueryOutcome, ResourceTracker, ServiceRuntimeEndpoint,
41};
42use linera_storage::{Clock as _, Storage};
43use linera_views::{
44 context::{Context, InactiveContext},
45 views::{ReplaceContext as _, RootView as _, View as _},
46};
47use tokio::sync::oneshot;
48use tracing::{debug, info, instrument, trace, warn};
49
50use crate::{
51 chain_worker::{handle::AtomicTimestamp, ChainWorkerConfig, DeliveryNotifier},
52 client::{ChainModes, ListeningMode},
53 data_types::{ChainInfo, ChainInfoQuery, ChainInfoResponse, CrossChainRequest},
54 worker::{BatchRequest, NetworkActions, Notification, Reason, WorkerError},
55};
56
57pub(crate) type EventSubscriptionsResult = Vec<((ChainId, StreamId), EventSubscriptions)>;
59
60#[cfg(with_metrics)]
61mod metrics {
62 use std::sync::LazyLock;
63
64 use linera_base::prometheus_util::{
65 exponential_bucket_interval, exponential_bucket_latencies, register_histogram,
66 register_histogram_vec, register_int_counter, register_int_counter_vec,
67 };
68 use prometheus::{Histogram, HistogramVec, IntCounter, IntCounterVec};
69
70 pub static CREATE_NETWORK_ACTIONS_LATENCY: LazyLock<Histogram> = LazyLock::new(|| {
71 register_histogram(
72 "create_network_actions_latency",
73 "Time (ms) to create network actions",
74 exponential_bucket_latencies(10_000.0),
75 )
76 });
77
78 pub static NUM_INBOXES: LazyLock<HistogramVec> = LazyLock::new(|| {
79 register_histogram_vec(
80 "num_inboxes",
81 "Number of inboxes",
82 &[],
83 exponential_bucket_interval(1.0, 10_000.0),
84 )
85 });
86
87 pub static BLOCK_PROPOSALS_RECEIVED_TOTAL: LazyLock<IntCounter> = LazyLock::new(|| {
88 register_int_counter(
89 "block_proposals_received_total",
90 "Total number of block proposals received by the worker",
91 )
92 });
93
94 pub static BLOCK_PROPOSALS_REJECTED_TOTAL: LazyLock<IntCounterVec> = LazyLock::new(|| {
95 register_int_counter_vec(
96 "block_proposals_rejected_total",
97 "Total number of block proposals rejected by the worker, labelled by error type",
98 &["error_type"],
99 )
100 });
101}
102
103pub(crate) struct ChainWorkerState<StorageClient>
105where
106 StorageClient: Storage,
107{
108 config: ChainWorkerConfig,
109 storage: StorageClient,
110 chain: ChainStateView<StorageClient::Context>,
111 service_runtime_endpoint: Option<ServiceRuntimeEndpoint>,
112 service_runtime_task: Option<web_thread_pool::Task<()>>,
117 last_access: Arc<AtomicTimestamp>,
122 block_values: Arc<ValueCache<CryptoHash, ConfirmedBlock>>,
123 execution_state_cache:
124 Option<Arc<UniqueValueCache<CryptoHash, ExecutionStateView<InactiveContext>>>>,
125 chain_modes: Option<Arc<sync::RwLock<ChainModes>>>,
126 delivery_notifier: DeliveryNotifier,
127 knows_chain_is_active: bool,
128 poisoned: bool,
131}
132
133pub(crate) enum CrossChainUpdateResult {
135 Updated(BlockHeight),
137 NothingToDo,
139 GapDetected {
143 origin: ChainId,
144 retransmit_from: BlockHeight,
145 },
146}
147
148pub enum BlockOutcome {
150 Processed,
151 Preprocessed,
152 Skipped,
153}
154
155#[derive(Clone, Copy, Debug, Eq, PartialEq)]
157pub enum ProcessConfirmedBlockMode {
158 Auto,
162 Execute,
166 Preprocess,
170}
171
172impl<StorageClient> ChainWorkerState<StorageClient>
173where
174 StorageClient: Storage + Clone + 'static,
175{
176 #[instrument(skip_all, fields(
178 chain_id = %chain_id
179 ))]
180 #[expect(clippy::too_many_arguments)]
181 pub(crate) async fn load(
182 config: ChainWorkerConfig,
183 storage: StorageClient,
184 block_values: Arc<ValueCache<CryptoHash, ConfirmedBlock>>,
185 execution_state_cache: Option<
186 Arc<UniqueValueCache<CryptoHash, ExecutionStateView<InactiveContext>>>,
187 >,
188 chain_modes: Option<Arc<sync::RwLock<ChainModes>>>,
189 delivery_notifier: DeliveryNotifier,
190 chain_id: ChainId,
191 service_runtime_endpoint: Option<ServiceRuntimeEndpoint>,
192 service_runtime_task: Option<web_thread_pool::Task<()>>,
193 ) -> Result<Self, WorkerError> {
194 let chain = storage.load_chain(chain_id).await?;
195
196 Ok(ChainWorkerState {
197 config,
198 storage,
199 chain,
200 service_runtime_endpoint,
201 service_runtime_task,
202 last_access: Arc::new(AtomicTimestamp::now()),
203 block_values,
204 execution_state_cache,
205 chain_modes,
206 delivery_notifier,
207 knows_chain_is_active: false,
208 poisoned: false,
209 })
210 }
211
212 fn chain_id(&self) -> ChainId {
214 self.chain.chain_id()
215 }
216
217 pub(crate) fn chain(&self) -> &ChainStateView<StorageClient::Context> {
219 &self.chain
220 }
221
222 pub(crate) fn knows_chain_is_active(&self) -> bool {
224 self.knows_chain_is_active
225 }
226
227 pub(crate) fn rollback(&mut self) {
229 self.chain.rollback();
230 }
231
232 pub(crate) fn check_not_poisoned(&self) -> Result<(), WorkerError> {
235 ensure!(!self.poisoned, WorkerError::PoisonedWorker);
236 Ok(())
237 }
238
239 pub(crate) fn touch(&self) {
241 self.last_access.store_now();
242 }
243
244 pub(crate) fn last_access_arc(&self) -> Arc<AtomicTimestamp> {
246 Arc::clone(&self.last_access)
247 }
248
249 pub(crate) fn clear_service_runtime(&mut self) -> Option<web_thread_pool::Task<()>> {
252 self.service_runtime_endpoint.take();
253 self.service_runtime_task.take()
254 }
255
256 pub(crate) async fn cross_chain_network_actions_if_reconciled(
260 &self,
261 ) -> Result<Option<NetworkActions>, WorkerError> {
262 let tracked = self.tracked_full_chains();
263 if !self.chain.outbox_index_is_reconciled(tracked.as_deref()) {
264 return Ok(None);
265 }
266 Ok(Some(
267 self.build_network_actions(None, tracked.as_deref().map(|h| h.inner()))
268 .await?,
269 ))
270 }
271
272 #[instrument(skip_all, fields(chain_id = %self.chain_id()))]
279 pub(crate) async fn reconcile_and_cross_chain_network_actions(
280 &mut self,
281 ) -> Result<NetworkActions, WorkerError> {
282 let tracked = self.tracked_full_chains();
283 self.chain
284 .reconcile_outbox_index(tracked.as_deref())
285 .await?;
286 let actions = self
287 .build_network_actions(None, tracked.as_deref().map(|h| h.inner()))
288 .await?;
289 self.save().await?;
290 Ok(actions)
291 }
292
293 #[tracing::instrument(level = "debug", skip(self))]
295 pub(crate) async fn handle_chain_info_query(
296 &mut self,
297 query: ChainInfoQuery,
298 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
299 let create_network_actions = query.create_network_actions;
300 if let Some((height, round)) = query.request_leader_timeout {
301 self.vote_for_leader_timeout(height, round).await?;
302 }
303 if query.request_fallback {
304 self.vote_for_fallback().await?;
305 }
306 let response = self.prepare_chain_info_response(query).await?;
307 let actions = if create_network_actions {
309 self.create_network_actions(None).await?
310 } else {
311 NetworkActions::default()
312 };
313 Ok((response, actions))
314 }
315
316 #[instrument(skip_all, fields(
318 chain_id = %self.chain_id(),
319 blob_id = %blob_id
320 ))]
321 pub(crate) async fn download_pending_blob(
322 &self,
323 blob_id: BlobId,
324 ) -> Result<CacheArc<Blob>, WorkerError> {
325 if let Some(blob) = self.chain.manager.pending_blob(&blob_id).await? {
326 return Ok(self.storage.cache_blob(blob));
327 }
328 self.storage
329 .read_blob(blob_id)
330 .await?
331 .ok_or(WorkerError::BlobsNotFound(vec![blob_id]))
332 }
333
334 #[instrument(skip_all, fields(
337 chain_id = %self.chain_id()
338 ))]
339 async fn get_required_blobs(
340 &self,
341 required_blob_ids: impl IntoIterator<Item = BlobId>,
342 created_blobs: BTreeMap<BlobId, Blob>,
343 ) -> Result<BTreeMap<BlobId, Blob>, WorkerError> {
344 let maybe_blobs = self
345 .maybe_get_required_blobs(required_blob_ids, Some(created_blobs))
346 .await?;
347 let not_found_blob_ids = missing_blob_ids(&maybe_blobs);
348 ensure!(
349 not_found_blob_ids.is_empty(),
350 WorkerError::BlobsNotFound(not_found_blob_ids)
351 );
352 Ok(maybe_blobs
353 .into_iter()
354 .filter_map(|(blob_id, maybe_blob)| Some((blob_id, maybe_blob?)))
355 .collect())
356 }
357
358 #[instrument(skip_all, fields(
360 chain_id = %self.chain_id()
361 ))]
362 async fn maybe_get_required_blobs(
363 &self,
364 blob_ids: impl IntoIterator<Item = BlobId>,
365 created_blobs: Option<BTreeMap<BlobId, Blob>>,
366 ) -> Result<BTreeMap<BlobId, Option<Blob>>, WorkerError> {
367 let maybe_blobs = blob_ids.into_iter().collect::<BTreeSet<_>>();
368 let mut maybe_blobs = maybe_blobs
369 .into_iter()
370 .map(|x| (x, None))
371 .collect::<Vec<(BlobId, Option<Blob>)>>();
372
373 if let Some(mut blob_map) = created_blobs {
374 for (blob_id, value) in &mut maybe_blobs {
375 if let Some(blob) = blob_map.remove(blob_id) {
376 *value = Some(blob);
377 }
378 }
379 }
380
381 let (missing_indices, missing_blob_ids) = missing_indices_blob_ids(&maybe_blobs);
382 let second_block_blobs = self.chain.manager.pending_blobs(&missing_blob_ids).await?;
383 for (index, blob) in missing_indices.into_iter().zip(second_block_blobs) {
384 maybe_blobs[index].1 = blob;
385 }
386
387 let (missing_indices, missing_blob_ids) = missing_indices_blob_ids(&maybe_blobs);
388 let third_block_blobs = self
389 .chain
390 .pending_validated_blobs
391 .multi_get(&missing_blob_ids)
392 .await?;
393 for (index, blob) in missing_indices.into_iter().zip(third_block_blobs) {
394 maybe_blobs[index].1 = blob;
395 }
396
397 let (missing_indices, missing_blob_ids) = missing_indices_blob_ids(&maybe_blobs);
398 if !missing_indices.is_empty() {
399 let all_entries_pending_blobs = self
400 .chain
401 .pending_proposed_blobs
402 .try_load_all_entries()
403 .await?;
404 for (index, blob_id) in missing_indices.into_iter().zip(missing_blob_ids) {
405 for (_, pending_blobs) in &all_entries_pending_blobs {
406 if let Some(blob) = pending_blobs.get(&blob_id).await? {
407 maybe_blobs[index].1 = Some(blob);
408 break;
409 }
410 }
411 }
412 }
413
414 let (missing_indices, missing_blob_ids) = missing_indices_blob_ids(&maybe_blobs);
415 let fourth_block_blobs = self.storage.read_blobs(&missing_blob_ids).await?;
416 for (index, blob) in missing_indices.into_iter().zip(fourth_block_blobs) {
417 maybe_blobs[index].1 = blob.map(CacheArc::unwrap_or_clone);
418 }
419 Ok(maybe_blobs.into_iter().collect())
420 }
421
422 #[instrument(skip_all, fields(
424 chain_id = %self.chain_id()
425 ))]
426 async fn create_cross_chain_actions_for_recipient(
427 &self,
428 recipient: ChainId,
429 ) -> Result<NetworkActions, WorkerError> {
430 let outbox = self.chain.outboxes.try_load_entry(&recipient).await?;
431 let Some(outbox) = outbox else {
432 return Ok(NetworkActions::default());
433 };
434 let heights = outbox.queue.elements().await?;
435 if heights.is_empty() {
436 return Ok(NetworkActions::default());
437 }
438 let heights_by_recipient = BTreeMap::from([(recipient, heights)]);
439 let cross_chain_requests = self
440 .create_cross_chain_requests(heights_by_recipient)
441 .await?;
442 Ok(NetworkActions {
443 cross_chain_requests,
444 notifications: Vec::new(),
445 })
446 }
447
448 fn tracked_full_chains(&self) -> Option<Arc<Hashed<ChainIdSet>>> {
451 let chain_modes = self.chain_modes.as_ref()?;
452 let full = chain_modes
453 .read()
454 .expect("Panics should not happen while holding a lock to `chain_modes`")
455 .full();
456 Some(full)
457 }
458
459 fn is_tracked(&self, chain_id: &ChainId) -> bool {
462 self.chain_modes.as_ref().is_none_or(|chain_modes| {
463 chain_modes
464 .read()
465 .expect("Panics should not happen while holding a lock to `chain_modes`")
466 .get(chain_id)
467 .is_some_and(ListeningMode::is_full)
468 })
469 }
470
471 async fn reconcile_tracked_outboxes(
474 &mut self,
475 ) -> Result<Option<Arc<Hashed<ChainIdSet>>>, WorkerError> {
476 let full_chains = self.tracked_full_chains();
477 self.chain
478 .reconcile_outbox_index(full_chains.as_deref())
479 .await?;
480 Ok(full_chains)
481 }
482
483 async fn create_network_actions(
486 &mut self,
487 old_round: Option<Round>,
488 ) -> Result<NetworkActions, WorkerError> {
489 let tracked = self.reconcile_tracked_outboxes().await?;
492 self.build_network_actions(old_round, tracked.as_deref().map(|h| h.inner()))
493 .await
494 }
495
496 async fn build_network_actions(
498 &self,
499 old_round: Option<Round>,
500 tracked: Option<&ChainIdSet>,
501 ) -> Result<NetworkActions, WorkerError> {
502 #[cfg(with_metrics)]
503 let _latency = metrics::CREATE_NETWORK_ACTIONS_LATENCY.measure_latency();
504 let mut heights_by_recipient = BTreeMap::<_, Vec<_>>::new();
505 let targets = self.chain.nonempty_outbox_chain_ids();
506 if let Some(tracked) = tracked {
507 if let Some(target) = targets.iter().find(|target| !tracked.contains(*target)) {
508 return Err(ChainError::CorruptedChainState(format!(
509 "outbox index contains untracked target {target}"
510 ))
511 .into());
512 }
513 }
514 let outboxes = self.chain.load_outboxes(&targets).await?;
515 for (target, outbox) in targets.into_iter().zip(outboxes) {
516 let heights = outbox.queue.elements().await?;
517 heights_by_recipient.insert(target, heights);
518 }
519 let cross_chain_requests = self
520 .create_cross_chain_requests(heights_by_recipient)
521 .await?;
522 let mut notifications = Vec::new();
523 if let Some(old_round) = old_round {
524 let round = self.chain.manager.current_round();
525 if round > old_round {
526 let height = self.chain.tip_state.get().next_block_height;
527 notifications.push(Notification {
528 chain_id: self.chain_id(),
529 reason: Reason::NewRound { height, round },
530 });
531 }
532 }
533 Ok(NetworkActions {
534 cross_chain_requests,
535 notifications,
536 })
537 }
538
539 async fn read_confirmed_blocks(
542 &self,
543 hashes: Vec<CryptoHash>,
544 ) -> Result<Vec<Option<CacheArc<ConfirmedBlock>>>, WorkerError> {
545 let mut blocks = Vec::with_capacity(hashes.len());
546 let mut uncached_indices = Vec::new();
547 let mut uncached_hashes = Vec::new();
548
549 for (i, hash) in hashes.iter().enumerate() {
550 if let Some(block) = self.block_values.get(hash) {
551 blocks.push(Some(block));
552 } else {
553 blocks.push(None);
554 uncached_indices.push(i);
555 uncached_hashes.push(*hash);
556 }
557 }
558
559 if !uncached_hashes.is_empty() {
560 let from_storage = self.storage.read_confirmed_blocks(uncached_hashes).await?;
561 for (i, maybe_block) in uncached_indices.into_iter().zip(from_storage) {
562 blocks[i] = maybe_block;
563 }
564 }
565
566 Ok(blocks)
567 }
568
569 #[instrument(skip_all, fields(
570 chain_id = %self.chain_id(),
571 num_recipients = %heights_by_recipient.len()
572 ))]
573 async fn create_cross_chain_requests(
574 &self,
575 heights_by_recipient: BTreeMap<ChainId, Vec<BlockHeight>>,
576 ) -> Result<Vec<CrossChainRequest>, WorkerError> {
577 let heights = heights_by_recipient
579 .values()
580 .flatten()
581 .copied()
582 .collect::<BTreeSet<_>>();
583 let hashes = self.chain.block_hashes(heights.iter().copied()).await?;
584
585 let blocks = self.read_confirmed_blocks(hashes.clone()).await?;
586
587 let mut height_to_blocks = HashMap::new();
588 for (block, hash) in blocks.into_iter().zip(hashes) {
589 let block = block.ok_or_else(|| WorkerError::ReadCertificatesError(vec![hash]))?;
590 let hashed_block = CacheArc::unwrap_or_clone(block).into_inner();
591 height_to_blocks.insert(hashed_block.inner().header.height, hashed_block);
592 }
593
594 let sender = self.chain.chain_id();
595 let mut cross_chain_requests = Vec::new();
596 for (recipient, heights) in heights_by_recipient {
597 let previous_height = heights.first().and_then(|first_height| {
601 let block = height_to_blocks.get(first_height)?;
602 let (_, prev_height) =
603 block.inner().body.previous_message_blocks.get(&recipient)?;
604 Some(*prev_height)
605 });
606 let mut bundles = Vec::new();
607 let mut bundles_size = 0;
608 for height in heights {
609 let Some(hashed_block) = height_to_blocks.get(&height) else {
610 tracing::warn!(
611 %height,
612 %recipient,
613 "spurious entry in outbox; skipping this and higher sender blocks"
614 );
615 break;
616 };
617 let new_bundles = hashed_block
618 .inner()
619 .message_bundles_for(recipient, hashed_block.hash())
620 .collect::<Vec<_>>();
621 let new_size = new_bundles
622 .iter()
623 .map(|(_epoch, bundle)| bundle.estimated_size())
624 .sum::<usize>();
625 if bundles_size + new_size > self.config.cross_chain_message_chunk_limit {
628 if bundles.is_empty() {
629 warn!(
630 "Single block at height {height} produces an UpdateRecipient \
631 of ~{new_size} bytes, exceeding the chunk limit of {}",
632 self.config.cross_chain_message_chunk_limit
633 );
634 } else {
635 debug!(
636 "Stopping cross-chain batch for {recipient} at height {height}: \
637 adding ~{new_size} bytes would exceed chunk limit of {} \
638 (current batch ~{bundles_size} bytes)",
639 self.config.cross_chain_message_chunk_limit
640 );
641 break;
642 }
643 }
644 bundles.extend(new_bundles);
645 bundles_size += new_size;
646 }
647 if !bundles.is_empty() {
648 cross_chain_requests.push(CrossChainRequest::UpdateRecipient {
649 sender,
650 recipient,
651 bundles,
652 previous_height,
653 });
654 }
655 }
656 Ok(cross_chain_requests)
657 }
658
659 #[instrument(skip_all, fields(
661 chain_id = %self.chain_id(),
662 height = %certificate.inner().height()
663 ))]
664 pub(crate) async fn process_timeout(
665 &mut self,
666 certificate: TimeoutCertificate,
667 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
668 self.initialize_and_save_if_needed().await?;
671 let (chain_epoch, committee) = self.chain.current_committee().await?;
672 certificate.check(&committee)?;
673 if self
674 .chain
675 .tip_state
676 .get()
677 .already_validated_block(certificate.inner().height())?
678 {
679 return Ok((self.chain_info_response().await?, NetworkActions::default()));
680 }
681 ensure!(
682 certificate.inner().epoch() == chain_epoch,
683 WorkerError::InvalidEpoch {
684 chain_id: certificate.inner().chain_id(),
685 chain_epoch,
686 epoch: certificate.inner().epoch()
687 }
688 );
689 let old_round = self.chain.manager.current_round();
690 self.chain
691 .manager
692 .handle_timeout_certificate(certificate, self.storage.clock().current_time());
693 self.save().await?;
694 let actions = self.create_network_actions(Some(old_round)).await?;
695 Ok((self.chain_info_response().await?, actions))
696 }
697
698 #[instrument(skip_all, fields(
703 chain_id = %self.chain_id(),
704 block_height = %proposal.content.block.height
705 ))]
706 async fn load_proposal_blobs(
707 &mut self,
708 proposal: &BlockProposal,
709 ) -> Result<Vec<Blob>, WorkerError> {
710 let owner = proposal.owner();
711 let BlockProposal {
712 content:
713 ProposalContent {
714 block,
715 round,
716 outcome: _,
717 },
718 original_proposal,
719 signature: _,
720 } = proposal;
721
722 let mut maybe_blobs = self
723 .maybe_get_required_blobs(proposal.required_blob_ids(), None)
724 .await?;
725 let missing_blob_ids = missing_blob_ids(&maybe_blobs);
726 if !missing_blob_ids.is_empty() {
727 let chain = &mut self.chain;
728 if chain.ownership().await?.open_multi_leader_rounds {
729 chain.pending_proposed_blobs.clear();
731 }
732 let validated = matches!(original_proposal, Some(OriginalProposal::Regular { .. }));
733 chain
734 .pending_proposed_blobs
735 .try_load_entry_mut(&owner)
736 .await?
737 .update(*round, validated, maybe_blobs)?;
738 self.save().await?;
739 return Err(WorkerError::BlobsNotFound(missing_blob_ids));
740 }
741 let published_blobs = block
742 .published_blob_ids()
743 .iter()
744 .filter_map(|blob_id| maybe_blobs.remove(blob_id).flatten())
745 .collect::<Vec<_>>();
746 Ok(published_blobs)
747 }
748
749 #[instrument(skip_all, fields(
751 chain_id = %self.chain_id(),
752 block_height = %certificate.block().header.height
753 ))]
754 pub(crate) async fn process_validated_block(
755 &mut self,
756 certificate: ValidatedBlockCertificate,
757 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
758 let block = certificate.block();
759
760 let header = &block.header;
761 let height = header.height;
762 self.initialize_and_save_if_needed().await?;
765 let tip_state = self.chain.tip_state.get();
766 ensure!(
767 header.height == tip_state.next_block_height,
768 ChainError::UnexpectedBlockHeight {
769 expected_block_height: tip_state.next_block_height,
770 found_block_height: header.height,
771 }
772 );
773 let (epoch, committee) = self.chain.current_committee().await?;
774 check_block_epoch(epoch, header.chain_id, header.epoch)?;
775 certificate.check(&committee)?;
776 let already_committed_block = self.chain.tip_state.get().already_validated_block(height)?;
777 let should_skip_validated_block = || {
778 self.chain
779 .manager
780 .check_validated_block(&certificate)
781 .map(|outcome| outcome == manager::Outcome::Skip)
782 };
783 if already_committed_block || should_skip_validated_block()? {
784 return Ok((
786 self.chain_info_response().await?,
787 NetworkActions::default(),
788 BlockOutcome::Skipped,
789 ));
790 }
791
792 self.block_values
793 .insert_hashed(Cow::Borrowed(certificate.inner().inner()));
794 let required_blob_ids = block.required_blob_ids();
795 let maybe_blobs = self
796 .maybe_get_required_blobs(required_blob_ids, Some(block.created_blobs()))
797 .await?;
798 let missing_blob_ids = missing_blob_ids(&maybe_blobs);
799 if !missing_blob_ids.is_empty() {
800 self.chain
801 .pending_validated_blobs
802 .update(certificate.round, true, maybe_blobs)?;
803 self.save().await?;
804 return Err(WorkerError::BlobsNotFound(missing_blob_ids));
805 }
806 let blobs = maybe_blobs
807 .into_iter()
808 .filter_map(|(blob_id, maybe_blob)| Some((blob_id, maybe_blob?)))
809 .collect();
810 let old_round = self.chain.manager.current_round();
811 self.chain.manager.create_final_vote(
812 certificate,
813 self.config.key_pair(),
814 self.storage.clock().current_time(),
815 blobs,
816 )?;
817 self.save().await?;
818 let actions = self.create_network_actions(Some(old_round)).await?;
819 Ok((
820 self.chain_info_response().await?,
821 actions,
822 BlockOutcome::Processed,
823 ))
824 }
825
826 async fn initialize_next_expected_events(&mut self) -> Result<(), WorkerError> {
833 if self.chain.next_expected_events.count().await? > 0 {
834 return Ok(()); }
836 for (stream_id, index) in self
837 .chain
838 .execution_state
839 .stream_event_counts
840 .index_values()
841 .await?
842 {
843 self.chain.next_expected_events.insert(&stream_id, index)?;
844 }
845 let chain_id = self.chain_id();
846 let index_values = self.chain.preprocessed_blocks.index_values().await?;
847 let hashes = index_values.iter().map(|(_, hash)| *hash).collect();
848 let blocks = self.read_confirmed_blocks(hashes).await?;
849 let tracked = self.reconcile_tracked_outboxes().await?;
850 for ((height, _), maybe_block) in index_values.into_iter().zip(blocks) {
851 let block =
852 maybe_block.ok_or_else(|| WorkerError::LocalBlockNotFound { height, chain_id })?;
853 self.chain
854 .preprocess_block(&block, tracked.as_deref().map(|h| h.inner()))
855 .await?;
856 }
857 Ok(())
858 }
859
860 #[instrument(skip_all, fields(
862 chain_id = %certificate.block().header.chain_id,
863 height = %certificate.block().header.height,
864 block_hash = %certificate.hash(),
865 ))]
866 pub(crate) async fn process_confirmed_block(
867 &mut self,
868 certificate: ConfirmedBlockCertificate,
869 mode: ProcessConfirmedBlockMode,
870 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
871 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
872 let block = certificate.block();
873 let height = block.header.height;
874 let chain_id = block.header.chain_id;
875
876 let tip = self.chain.tip_state.get().clone();
878 if tip.next_block_height > height {
879 let actions = self.create_network_actions(None).await?;
880 self.register_delivery_notifier(height, &actions, notify_when_messages_are_delivered)
881 .await;
882 return Ok((
883 self.chain_info_response().await?,
884 actions,
885 BlockOutcome::Skipped,
886 ));
887 }
888
889 let epoch = block.header.epoch;
893 let committee = self
894 .chain
895 .execution_state
896 .context()
897 .extra()
898 .get_committees(epoch..=epoch)
899 .await
900 .with_execution_context(ChainExecutionContext::Block)?
901 .remove(&epoch)
902 .ok_or_else(|| {
903 ChainError::InternalError(format!(
904 "missing committee for epoch {epoch}; this is a bug"
905 ))
906 })?;
907 certificate.check(&committee)?;
908
909 let required_blob_ids = block.required_blob_ids();
913 let blobs_result = self
914 .get_required_blobs(required_blob_ids.iter().copied(), block.created_blobs())
915 .await
916 .map(|blobs| blobs.into_values().collect::<Vec<_>>());
917
918 if let Ok(blobs) = &blobs_result {
919 self.storage
920 .write_blobs_and_certificate(blobs, &certificate)
921 .await?;
922 let events = block
923 .body
924 .events
925 .iter()
926 .flatten()
927 .map(|event| (event.id(chain_id), event.value.clone()));
928 self.storage.write_events(events).await?;
929 }
930
931 let blob_state = certificate.value().to_blob_state(blobs_result.is_ok());
933 let blob_ids = required_blob_ids.into_iter().collect::<Vec<_>>();
934 self.storage
935 .maybe_write_blob_states(&blob_ids, blob_state)
936 .await?;
937
938 let blobs = blobs_result?
939 .into_iter()
940 .map(|blob| (blob.id(), blob))
941 .collect::<BTreeMap<_, _>>();
942
943 use ProcessConfirmedBlockMode::{Auto, Execute, Preprocess};
948 let gap = tip.next_block_height < height;
949 match (mode, gap) {
950 (Preprocess, _) | (Auto, true) => {
951 self.preprocess_certified_block(certificate, notify_when_messages_are_delivered)
952 .await
953 }
954 (Execute, true) => Err(WorkerError::InvalidBlockChaining),
955 (Auto | Execute, false) => {
956 self.execute_contiguous_block(
957 certificate,
958 blobs,
959 tip,
960 notify_when_messages_are_delivered,
961 )
962 .await
963 }
964 }
965 }
966
967 async fn preprocess_certified_block(
970 &mut self,
971 certificate: ConfirmedBlockCertificate,
972 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
973 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
974 let block_hash = certificate.hash();
975 let block = certificate.block();
976 let chain_id = block.header.chain_id;
977 let height = block.header.height;
978
979 if block.body.events.iter().any(|events| !events.is_empty()) {
980 self.initialize_next_expected_events().await?;
981 }
982 let tracked = self.reconcile_tracked_outboxes().await?;
983 let updated_event_streams = self
984 .chain
985 .preprocess_block(certificate.value(), tracked.as_deref().map(|h| h.inner()))
986 .await?;
987 self.save().await?;
988 let mut actions = self.create_network_actions(None).await?;
989 if !updated_event_streams.is_empty() {
990 actions.notifications.push(Notification {
991 chain_id,
992 reason: Reason::NewEvents {
993 height,
994 hash: block_hash,
995 event_streams: updated_event_streams,
996 },
997 });
998 }
999 trace!("Preprocessed confirmed block {height}");
1000 self.register_delivery_notifier(height, &actions, notify_when_messages_are_delivered)
1001 .await;
1002 Ok((
1003 self.chain_info_response().await?,
1004 actions,
1005 BlockOutcome::Preprocessed,
1006 ))
1007 }
1008
1009 async fn execute_contiguous_block(
1012 &mut self,
1013 certificate: ConfirmedBlockCertificate,
1014 mut blobs: BTreeMap<BlobId, Blob>,
1015 tip: ChainTipState,
1016 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1017 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
1018 let block_hash = certificate.hash();
1019 let block = certificate.block();
1020 let chain_id = block.header.chain_id;
1021 let height = block.header.height;
1022
1023 ensure!(
1025 tip.block_hash == block.header.previous_block_hash,
1026 WorkerError::InvalidBlockChaining
1027 );
1028
1029 self.initialize_and_save_if_needed().await?;
1032 let (epoch, _) = self.chain.current_committee().await?;
1033 check_block_epoch(epoch, chain_id, block.header.epoch)?;
1034
1035 let published_blobs = block
1036 .published_blob_ids()
1037 .iter()
1038 .filter_map(|blob_id| blobs.remove(blob_id))
1039 .collect::<Vec<_>>();
1040
1041 if block.body.events.iter().any(|events| !events.is_empty()) {
1042 self.initialize_next_expected_events().await?;
1044 }
1045
1046 let local_time = self.storage.clock().current_time();
1047 if block.header.timestamp.duration_since(local_time) > self.config.block_time_grace_period {
1048 warn!(
1049 block_timestamp = %block.header.timestamp,
1050 %local_time,
1051 "Confirmed block has a timestamp in the future beyond the block time grace period"
1052 );
1053 }
1054 let tracked = self.reconcile_tracked_outboxes().await?;
1055 let chain = &mut self.chain;
1056 chain
1057 .remove_bundles_from_inboxes(
1058 block.header.timestamp,
1059 false,
1060 block.body.incoming_bundles(),
1061 )
1062 .await?;
1063 let confirmed_block = if let Some(mut execution_state) = self
1064 .execution_state_cache
1065 .as_ref()
1066 .and_then(|cache| cache.remove(&block_hash))
1067 {
1068 chain.execution_state = execution_state
1069 .with_context(|ctx| {
1070 chain
1071 .execution_state
1072 .context()
1073 .clone_with_base_key(ctx.base_key().bytes.clone())
1074 })
1075 .await;
1076 certificate.into_value()
1077 } else {
1078 let (proposed_block, outcome) = block.clone().into_proposal();
1079 let (proposed_block, verified, _resource_tracker, _) = chain
1080 .execute_block(
1081 proposed_block,
1082 local_time,
1083 None,
1084 &published_blobs,
1085 BlockExecution::HandleConfirmed {
1086 oracle_responses: outcome.oracle_responses.clone(),
1087 },
1088 )
1089 .await?;
1090 if outcome != verified {
1092 return Err(ChainError::CorruptedChainState(format!(
1093 "computed block outcome differs from the certificate.\n\
1094 Computed: {verified:#?}\n\
1095 Submitted: {outcome:#?}"
1096 ))
1097 .into());
1098 }
1099 ConfirmedBlock::new(Block::new(proposed_block, verified))
1100 };
1101
1102 let event_streams = chain
1103 .apply_confirmed_block(
1104 &confirmed_block,
1105 local_time,
1106 tracked.as_deref().map(|h| h.inner()),
1107 )
1108 .await?;
1109 let mut actions = self.create_network_actions(None).await?;
1110 trace!("Processed confirmed block {height}");
1111 let hash = confirmed_block.inner().hash();
1112 actions.notifications.push(Notification {
1113 chain_id,
1114 reason: Reason::NewBlock {
1115 height,
1116 hash,
1117 event_streams: event_streams.clone(),
1118 },
1119 });
1120 if !event_streams.is_empty() {
1121 actions.notifications.push(Notification {
1122 chain_id,
1123 reason: Reason::NewEvents {
1124 height,
1125 hash,
1126 event_streams,
1127 },
1128 });
1129 }
1130 self.save().await?;
1131
1132 self.block_values
1133 .insert_hashed(Cow::Owned(confirmed_block.into_inner()));
1134
1135 self.register_delivery_notifier(height, &actions, notify_when_messages_are_delivered)
1136 .await;
1137
1138 Ok((
1139 self.chain_info_response().await?,
1140 actions,
1141 BlockOutcome::Processed,
1142 ))
1143 }
1144
1145 #[instrument(level = "trace", skip(self, notify_when_messages_are_delivered))]
1148 async fn register_delivery_notifier(
1149 &self,
1150 height: BlockHeight,
1151 actions: &NetworkActions,
1152 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1153 ) {
1154 if let Some(notifier) = notify_when_messages_are_delivered {
1155 if actions
1156 .cross_chain_requests
1157 .iter()
1158 .any(|request| request.has_messages_lower_or_equal_than(height))
1159 {
1160 self.delivery_notifier.register(height, notifier);
1161 } else {
1162 if let Err(()) = notifier.send(()) {
1165 debug!("Failed to notify message delivery to caller (early case)");
1166 }
1167 }
1168 }
1169 }
1170
1171 #[instrument(level = "debug", skip(self, bundles), fields(chain_id = %self.chain_id()))]
1173 pub(crate) async fn process_cross_chain_update(
1174 &mut self,
1175 origin: ChainId,
1176 bundles: Vec<(Epoch, MessageBundle)>,
1177 previous_height: Option<BlockHeight>,
1178 ) -> Result<CrossChainUpdateResult, WorkerError> {
1179 let mut inbox = self.chain.inboxes.try_load_entry_mut(&origin).await?;
1181 let next_height_to_receive = inbox.next_block_height_to_receive()?;
1182 let last_anticipated_block_height = match inbox.removed_bundles.back().await? {
1183 Some(bundle) => Some(bundle.height),
1184 None => None,
1185 };
1186
1187 if let Some(prev) = previous_height {
1190 if prev >= next_height_to_receive {
1191 let chain_id = self.chain_id();
1192 if self.config.allow_revert_confirm && self.config.recovery_allowed_for(&chain_id) {
1193 warn!(
1194 %chain_id,
1195 "Inbox gap detected from {origin}: \
1196 sender declares previous height {prev} but we only have up to \
1197 {next_height_to_receive}; requesting resend",
1198 );
1199 return Ok(CrossChainUpdateResult::GapDetected {
1200 origin,
1201 retransmit_from: next_height_to_receive,
1202 });
1203 }
1204 return Err(ChainError::InboxGapDetected {
1205 chain_id,
1206 origin,
1207 expected_height: prev,
1208 actual_height: bundles.first().map(|(_, b)| b.height).unwrap_or_default(),
1209 }
1210 .into());
1211 }
1212 }
1213
1214 let helper = CrossChainUpdateHelper::new(&self.config, &self.chain);
1215 let recipient = self.chain_id();
1216 let bundles = helper
1217 .select_message_bundles(
1218 &origin,
1219 recipient,
1220 next_height_to_receive,
1221 last_anticipated_block_height,
1222 bundles,
1223 &self.storage,
1224 )
1225 .await?;
1226 let Some(last_updated_height) = bundles.last().map(|bundle| bundle.height) else {
1227 return Ok(CrossChainUpdateResult::NothingToDo);
1228 };
1229 let local_time = self.storage.clock().current_time();
1231 let mut previous_height = None;
1232 for bundle in bundles {
1233 let add_to_received_log = previous_height != Some(bundle.height);
1234 previous_height = Some(bundle.height);
1235 self.chain
1237 .receive_message_bundle_with_inbox(
1238 &mut inbox,
1239 &origin,
1240 bundle,
1241 local_time,
1242 add_to_received_log,
1243 )
1244 .await?;
1245 }
1246 inbox.observe_size_metric();
1247 drop(inbox);
1248 if !self.config.allow_inactive_chains && !self.chain.is_active().await? {
1249 warn!(
1253 chain_id = %self.chain_id(),
1254 "Refusing to deliver messages from {origin} \
1255 at height {last_updated_height} because the recipient is still inactive",
1256 );
1257 return Ok(CrossChainUpdateResult::NothingToDo);
1258 }
1259 Ok(CrossChainUpdateResult::Updated(last_updated_height))
1260 }
1261
1262 #[instrument(skip_all, fields(
1264 chain_id = %self.chain_id(),
1265 %recipient,
1266 %latest_height
1267 ))]
1268 pub(crate) async fn confirm_updated_recipient(
1269 &mut self,
1270 recipient: ChainId,
1271 latest_height: BlockHeight,
1272 ) -> Result<bool, WorkerError> {
1273 let tracked = self.reconcile_tracked_outboxes().await?;
1276 Ok(self
1279 .chain
1280 .mark_messages_as_received(
1281 &recipient,
1282 latest_height,
1283 tracked.as_deref().map(|h| h.inner()),
1284 )
1285 .await?
1286 && self.chain.all_messages_delivered_up_to(latest_height))
1287 }
1288
1289 pub(crate) fn notify_delivery(&self, height: BlockHeight) {
1291 self.delivery_notifier.notify(height);
1292 }
1293
1294 pub(crate) async fn process_batch(
1299 &mut self,
1300 requests: Vec<BatchRequest>,
1301 ) -> Result<(), WorkerError> {
1302 let mut update_results = Vec::new();
1303 let mut confirm_results = Vec::new();
1304 let mut need_save = false;
1305 let mut need_rollback = false;
1306 let mut recovery_error = None;
1307 let mut max_delivered_height: Option<BlockHeight> = None;
1308
1309 for request in requests {
1310 match request {
1311 BatchRequest::Update {
1312 origin,
1313 bundles,
1314 previous_height,
1315 result_sender,
1316 } => {
1317 if need_rollback {
1318 send_result(result_sender, Err(WorkerError::BatchRolledBack));
1319 continue;
1320 }
1321 let result = self
1322 .process_cross_chain_update(origin, bundles, previous_height)
1323 .await;
1324 let update_result = match result {
1325 Ok(update_result) => update_result,
1326 Err(error) => {
1327 need_rollback = true;
1328 let (recovery, to_send) = classify_processing_error(error);
1329 recovery_error = recovery_error.or(recovery);
1330 send_result(result_sender, Err(to_send));
1331 continue;
1332 }
1333 };
1334 match &update_result {
1335 CrossChainUpdateResult::Updated(_) => need_save = true,
1336 CrossChainUpdateResult::GapDetected { .. }
1337 | CrossChainUpdateResult::NothingToDo => {}
1338 }
1339 update_results.push((result_sender, update_result));
1340 }
1341 BatchRequest::Confirm {
1342 recipient,
1343 latest_height,
1344 result_sender,
1345 } => {
1346 if need_rollback {
1347 send_result(result_sender, Err(WorkerError::BatchRolledBack));
1348 continue;
1349 }
1350 match self
1351 .confirm_updated_recipient(recipient, latest_height)
1352 .await
1353 {
1354 Ok(fully_delivered) => {
1355 need_save = true;
1356 if fully_delivered {
1357 max_delivered_height = Some(
1358 max_delivered_height
1359 .map_or(latest_height, |h| h.max(latest_height)),
1360 );
1361 }
1362 confirm_results.push((result_sender, recipient));
1363 }
1364 Err(error) => {
1365 need_rollback = true;
1366 let (recovery, to_send) = classify_processing_error(error);
1367 recovery_error = recovery_error.or(recovery);
1368 send_result(result_sender, Err(to_send));
1369 }
1370 }
1371 }
1372 }
1373 }
1374 let mut save_error = None;
1375 if !need_rollback && need_save {
1376 if let Err(error) = self.save().await {
1377 tracing::error!(%error, "failed to save batch; rolling back");
1378 need_rollback = true;
1379 save_error = Some(error);
1380 }
1381 }
1382 if need_rollback {
1383 for (result_sender, _) in update_results {
1384 send_result(result_sender, Err(WorkerError::BatchRolledBack));
1385 }
1386 for (result_sender, _) in confirm_results {
1387 send_result(result_sender, Err(WorkerError::BatchRolledBack));
1388 }
1389 return match save_error.or(recovery_error) {
1399 Some(error) => Err(error),
1400 None => Ok(()),
1401 };
1402 }
1403
1404 if let Some(height) = max_delivered_height {
1405 self.notify_delivery(height);
1406 }
1407
1408 for (result_sender, update_result) in update_results {
1409 send_result(result_sender, Ok(update_result));
1410 }
1411 for (result_sender, recipient) in confirm_results {
1412 let result = self
1413 .create_cross_chain_actions_for_recipient(recipient)
1414 .await;
1415 send_result(result_sender, result);
1416 }
1417 Ok(())
1418 }
1419
1420 #[instrument(skip_all, fields(
1425 chain_id = %self.chain_id(),
1426 %recipient,
1427 %retransmit_from,
1428 ))]
1429 pub(crate) async fn handle_revert_confirm(
1430 &mut self,
1431 recipient: ChainId,
1432 retransmit_from: BlockHeight,
1433 ) -> Result<NetworkActions, WorkerError> {
1434 self.reconcile_tracked_outboxes().await?;
1435 let Some(latest_height) = self.chain.previous_message_blocks.get(&recipient).await? else {
1438 warn!("RevertConfirm: no record of sending to {recipient}");
1439 return Ok(NetworkActions::default());
1440 };
1441
1442 let mut heights_to_re_add = Vec::new();
1443 let mut current_height = latest_height;
1444 while current_height >= retransmit_from {
1445 heights_to_re_add.push(current_height);
1449 let hash = match &*self.chain.block_hashes([current_height]).await? {
1451 [hash] => *hash,
1452 _ => {
1453 return Err(WorkerError::ConfirmedBlockHashNotFound {
1454 height: current_height,
1455 chain_id: self.chain_id(),
1456 })
1457 }
1458 };
1459 let block = self
1460 .read_confirmed_blocks(vec![hash])
1461 .await?
1462 .pop()
1463 .flatten()
1464 .ok_or_else(|| WorkerError::LocalBlockNotFound {
1465 height: current_height,
1466 chain_id: self.chain_id(),
1467 })?;
1468 match block.block().body.previous_message_blocks.get(&recipient) {
1469 Some((_, prev_height)) if *prev_height >= retransmit_from => {
1470 current_height = *prev_height;
1471 }
1472 _ => break,
1473 }
1474 }
1475
1476 let new_heights = self
1478 .chain
1479 .outboxes
1480 .try_load_entry_mut(&recipient)
1481 .await?
1482 .revert(&heights_to_re_add)
1483 .await?;
1484
1485 if new_heights.is_empty() {
1486 debug!("RevertConfirm: all heights already in outbox for {recipient}");
1487 return Ok(NetworkActions::default());
1488 }
1489
1490 let new_heights_len = new_heights.len();
1493 if self.is_tracked(&recipient) {
1494 for h in new_heights {
1495 *self.chain.outbox_counters.get_mut().entry(h).or_default() += 1;
1496 }
1497 self.chain.nonempty_outboxes.get_mut().insert(recipient);
1498 }
1499
1500 let actions = self
1502 .create_cross_chain_actions_for_recipient(recipient)
1503 .await?;
1504
1505 self.save().await?;
1507
1508 warn!(
1509 "RevertConfirm: re-added {new_heights_len} heights to outbox for {recipient}, \
1510 starting from height {retransmit_from}"
1511 );
1512
1513 Ok(actions)
1514 }
1515
1516 pub(crate) async fn maybe_reset_corrupted_chain_state(
1520 &mut self,
1521 ) -> Result<Option<Vec<CrossChainRequest>>, WorkerError> {
1522 let Some(min_duration) = self.config.reset_on_corrupted_chain_state else {
1523 return Ok(None);
1524 };
1525 let chain_id = self.chain_id();
1526 if !self.config.recovery_allowed_for(&chain_id) {
1527 return Ok(None);
1528 }
1529 let local_time = self.storage.clock().current_time();
1530 let block_zero_time = *self.chain.block_zero_executed_at.get();
1531 let elapsed = local_time.duration_since(block_zero_time);
1532 if elapsed < min_duration {
1533 warn!(
1534 %chain_id, ?elapsed, ?min_duration,
1535 "Not resetting corrupted chain state; not enough time elapsed \
1536 since last block 0 execution"
1537 );
1538 return Ok(None);
1539 }
1540 warn!(%chain_id, "Corrupted chain state detected; resetting and re-executing");
1541 Ok(Some(self.reset_and_reexecute_chain().await?))
1542 }
1543
1544 #[instrument(skip_all, fields(
1548 chain_id = %self.chain_id(),
1549 ))]
1550 pub(crate) async fn reset_and_reexecute_chain(
1551 &mut self,
1552 ) -> Result<Vec<CrossChainRequest>, WorkerError> {
1553 let chain_id = self.chain_id();
1554 let tip_height = self.chain.tip_state.get().next_block_height;
1555
1556 let sender_ids = self.chain.inboxes.indices().await?;
1558 let hashes = self.chain.confirmed_log.read(..).await?;
1559 let preprocessed = self.chain.preprocessed_blocks.index_values().await?;
1560
1561 let manager_snapshot = ManagerSafetySnapshot::capture(&self.chain.manager).await?;
1564
1565 self.chain.clear();
1567 self.knows_chain_is_active = false;
1568 self.save().await?;
1569 warn!(
1570 %chain_id,
1571 "Cleared chain state up to height {tip_height}; \
1572 re-executing all blocks"
1573 );
1574
1575 let num_confirmed = hashes.len();
1577 let num_preprocessed = preprocessed.len();
1578 for (index, hash) in hashes.into_iter().enumerate() {
1579 let height = BlockHeight(index as u64);
1580 if index % 1000 == 0 {
1581 info!(
1582 %chain_id, confirmed = index, total = num_confirmed,
1583 "Re-executing confirmed blocks after reset"
1584 );
1585 }
1586 let cert = self
1587 .storage
1588 .read_certificate(hash)
1589 .await?
1590 .map(CacheArc::unwrap_or_clone)
1591 .ok_or_else(|| WorkerError::LocalBlockNotFound { height, chain_id })?;
1592 Box::pin(self.process_confirmed_block(cert, ProcessConfirmedBlockMode::Execute, None))
1593 .await?;
1594 }
1595 for (index, (height, hash)) in preprocessed.into_iter().enumerate() {
1596 if index % 1000 == 0 {
1597 info!(
1598 %chain_id, preprocessed = index, total = num_preprocessed,
1599 "Re-preprocessing blocks after reset"
1600 );
1601 }
1602 let cert = self
1603 .storage
1604 .read_certificate(hash)
1605 .await?
1606 .map(CacheArc::unwrap_or_clone)
1607 .ok_or_else(|| WorkerError::LocalBlockNotFound { height, chain_id })?;
1608 Box::pin(self.process_confirmed_block(
1609 cert,
1610 ProcessConfirmedBlockMode::Preprocess,
1611 None,
1612 ))
1613 .await?;
1614 }
1615
1616 let new_tip_height = self.chain.tip_state.get().next_block_height;
1624 if new_tip_height == tip_height {
1625 manager_snapshot.restore(&mut self.chain.manager)?;
1626 self.save().await?;
1627 } else {
1628 warn!(
1629 %tip_height, %new_tip_height,
1630 "Dropping manager snapshot: pre-reset tip differs from post-reset tip"
1631 );
1632 }
1633
1634 let revert_requests = sender_ids
1637 .into_iter()
1638 .map(|sender| CrossChainRequest::RevertConfirm {
1639 sender,
1640 recipient: chain_id,
1641 retransmit_from: BlockHeight::ZERO,
1642 })
1643 .collect::<Vec<_>>();
1644
1645 warn!(
1646 tip_height = %self.chain.tip_state.get().next_block_height,
1647 num_revert_confirms = revert_requests.len(),
1648 "Chain reset and re-executed; sending RevertConfirm to senders"
1649 );
1650
1651 Ok(revert_requests)
1652 }
1653
1654 #[instrument(skip_all, fields(
1655 chain_id = %self.chain_id(),
1656 num_trackers = %new_trackers.len()
1657 ))]
1658 pub(crate) async fn update_received_certificate_trackers(
1659 &mut self,
1660 new_trackers: BTreeMap<ValidatorPublicKey, u64>,
1661 ) -> Result<(), WorkerError> {
1662 self.chain
1663 .update_received_certificate_trackers(new_trackers);
1664 self.save().await?;
1665 Ok(())
1666 }
1667
1668 #[instrument(skip_all, fields(
1670 chain_id = %self.chain_id(),
1671 start = %start,
1672 end = %end
1673 ))]
1674 pub(crate) async fn get_preprocessed_block_hashes(
1675 &self,
1676 start: BlockHeight,
1677 end: BlockHeight,
1678 ) -> Result<Vec<CryptoHash>, WorkerError> {
1679 let mut hashes = Vec::new();
1680 let mut height = start;
1681 while height < end {
1682 match self.chain.preprocessed_blocks.get(&height).await? {
1683 Some(hash) => hashes.push(hash),
1684 None => break,
1685 }
1686 height = height.try_add_one()?;
1687 }
1688 Ok(hashes)
1689 }
1690
1691 #[instrument(skip_all, fields(
1693 chain_id = %self.chain_id(),
1694 origin = %origin
1695 ))]
1696 pub(crate) async fn get_inbox_next_height(
1697 &self,
1698 origin: ChainId,
1699 ) -> Result<BlockHeight, WorkerError> {
1700 Ok(match self.chain.inboxes.try_load_entry(&origin).await? {
1701 Some(inbox) => inbox.next_block_height_to_receive()?,
1702 None => BlockHeight::ZERO,
1703 })
1704 }
1705
1706 #[instrument(skip_all, fields(
1709 chain_id = %self.chain_id(),
1710 num_blob_ids = %blob_ids.len()
1711 ))]
1712 pub(crate) async fn get_locking_blobs(
1713 &self,
1714 blob_ids: Vec<BlobId>,
1715 ) -> Result<Option<Vec<Blob>>, WorkerError> {
1716 let results = self
1717 .chain
1718 .manager
1719 .locking_blobs
1720 .multi_get(&blob_ids)
1721 .await?;
1722 Ok(results.into_iter().collect())
1723 }
1724
1725 pub(crate) async fn get_block_hashes(
1727 &self,
1728 heights: Vec<BlockHeight>,
1729 ) -> Result<Vec<CryptoHash>, WorkerError> {
1730 Ok(self.chain.block_hashes(heights).await?)
1731 }
1732
1733 pub(crate) async fn get_proposed_blobs(
1735 &self,
1736 blob_ids: Vec<BlobId>,
1737 ) -> Result<Vec<Blob>, WorkerError> {
1738 let results = self
1739 .chain
1740 .manager
1741 .proposed_blobs
1742 .multi_get(&blob_ids)
1743 .await?;
1744 let mut blobs = Vec::with_capacity(blob_ids.len());
1745 let mut missing = Vec::new();
1746 for (blob_id, maybe_blob) in blob_ids.into_iter().zip(results) {
1747 match maybe_blob {
1748 Some(blob) => blobs.push(blob),
1749 None => missing.push(blob_id),
1750 }
1751 }
1752 if !missing.is_empty() {
1753 return Err(WorkerError::BlobsNotFound(missing));
1754 }
1755 Ok(blobs)
1756 }
1757
1758 pub(crate) async fn get_previous_event_blocks(
1760 &self,
1761 stream_ids: Vec<StreamId>,
1762 ) -> Result<BTreeMap<StreamId, (BlockHeight, CryptoHash)>, WorkerError> {
1763 let heights = self
1764 .chain
1765 .previous_event_blocks
1766 .multi_get(&stream_ids)
1767 .await?;
1768 let mut result = BTreeMap::new();
1769 let mut indices = Vec::new();
1770 let mut streams_with_heights = Vec::new();
1771 for (stream_id, height) in stream_ids.into_iter().zip(heights) {
1772 if let Some(height) = height {
1773 let index = usize::try_from(height.0).map_err(|_| ArithmeticError::Overflow)?;
1774 indices.push(index);
1775 streams_with_heights.push((stream_id, height));
1776 }
1777 }
1778 let hashes = self.chain.confirmed_log.multi_get(indices).await?;
1779 for (hash, (stream_id, height)) in hashes.into_iter().zip(streams_with_heights) {
1780 if let Some(hash) = hash {
1781 result.insert(stream_id, (height, hash));
1782 }
1783 }
1784 Ok(result)
1785 }
1786
1787 pub(crate) async fn get_next_expected_events(
1789 &self,
1790 stream_ids: Vec<StreamId>,
1791 ) -> Result<BTreeMap<StreamId, u32>, WorkerError> {
1792 let values = self
1793 .chain
1794 .next_expected_events
1795 .multi_get(&stream_ids)
1796 .await?;
1797 Ok(stream_ids
1798 .into_iter()
1799 .zip(values)
1800 .filter_map(|(id, val)| Some((id, val?)))
1801 .collect())
1802 }
1803
1804 pub(crate) async fn get_event_subscriptions(
1806 &self,
1807 ) -> Result<EventSubscriptionsResult, WorkerError> {
1808 Ok(self
1809 .chain
1810 .execution_state
1811 .system
1812 .event_subscriptions
1813 .index_values()
1814 .await?)
1815 }
1816
1817 pub(crate) async fn get_stream_event_count(
1819 &self,
1820 stream_id: StreamId,
1821 ) -> Result<Option<u32>, WorkerError> {
1822 let next_expected = self.chain.next_expected_events.get(&stream_id).await?;
1827 if next_expected.is_some() {
1828 return Ok(next_expected);
1829 }
1830 Ok(self
1831 .chain
1832 .execution_state
1833 .stream_event_counts
1834 .get(&stream_id)
1835 .await?)
1836 }
1837
1838 pub(crate) async fn get_received_certificate_trackers(
1840 &self,
1841 ) -> Result<HashMap<ValidatorPublicKey, u64>, WorkerError> {
1842 Ok(self.chain.received_certificate_trackers.get().clone())
1843 }
1844
1845 pub(crate) async fn get_tip_state_and_outbox_info(
1847 &self,
1848 receiver_id: ChainId,
1849 ) -> Result<(BlockHeight, Option<BlockHeight>), WorkerError> {
1850 let next_block_height = self.chain.tip_state.get().next_block_height;
1851 let next_height_to_schedule = self
1852 .chain
1853 .outboxes
1854 .try_load_entry(&receiver_id)
1855 .await?
1856 .map(|outbox| *outbox.next_height_to_schedule.get());
1857 Ok((next_block_height, next_height_to_schedule))
1858 }
1859
1860 pub(crate) async fn get_next_height_to_preprocess(&self) -> Result<BlockHeight, WorkerError> {
1862 Ok(self.chain.next_height_to_preprocess().await?)
1863 }
1864
1865 pub(crate) async fn get_manager_seed(&self) -> Result<u64, WorkerError> {
1867 Ok(*self.chain.manager.seed.get())
1868 }
1869
1870 #[instrument(skip_all, fields(
1872 chain_id = %self.chain_id(),
1873 height = %height,
1874 round = %round
1875 ))]
1876 async fn vote_for_leader_timeout(
1877 &mut self,
1878 height: BlockHeight,
1879 round: Round,
1880 ) -> Result<(), WorkerError> {
1881 let chain = &mut self.chain;
1882 ensure!(
1883 height == chain.tip_state.get().next_block_height,
1884 WorkerError::UnexpectedBlockHeight {
1885 expected_block_height: chain.tip_state.get().next_block_height,
1886 found_block_height: height
1887 }
1888 );
1889 let epoch = chain.execution_state.system.epoch.get();
1890 let chain_id = chain.chain_id();
1891 let key_pair = self.config.key_pair();
1892 let local_time = self.storage.clock().current_time();
1893 if chain
1894 .manager
1895 .create_timeout_vote(chain_id, height, round, *epoch, key_pair, local_time)?
1896 {
1897 self.save().await?;
1898 }
1899 Ok(())
1900 }
1901
1902 #[instrument(skip_all, fields(
1905 chain_id = %self.chain_id()
1906 ))]
1907 async fn vote_for_fallback(&mut self) -> Result<(), WorkerError> {
1908 Err(WorkerError::NoFallbackMode)
1909 }
1910
1911 #[instrument(skip_all, fields(
1912 chain_id = %self.chain_id(),
1913 blob_id = %blob.id()
1914 ))]
1915 pub(crate) async fn handle_pending_blob(
1916 &mut self,
1917 blob: Blob,
1918 ) -> Result<ChainInfoResponse, WorkerError> {
1919 let mut was_expected = self
1920 .chain
1921 .pending_validated_blobs
1922 .maybe_insert(&blob)
1923 .await?;
1924 for (_, mut pending_blobs) in self
1925 .chain
1926 .pending_proposed_blobs
1927 .try_load_all_entries_mut()
1928 .await?
1929 {
1930 if !pending_blobs.validated.get() {
1931 let (_, committee) = self.chain.current_committee().await?;
1932 let policy = committee.policy();
1933 policy
1934 .check_blob_size(blob.content())
1935 .with_execution_context(ChainExecutionContext::Block)?;
1936 ensure!(
1937 u64::try_from(pending_blobs.pending_blobs.count().await?)
1938 .is_ok_and(|count| count < policy.maximum_published_blobs),
1939 WorkerError::TooManyPublishedBlobs(policy.maximum_published_blobs)
1940 );
1941 }
1942 was_expected = was_expected || pending_blobs.maybe_insert(&blob).await?;
1943 }
1944 ensure!(was_expected, WorkerError::UnexpectedBlob);
1945 self.save().await?;
1946 self.chain_info_response().await
1947 }
1948
1949 #[cfg(with_testing)]
1951 #[instrument(skip_all, fields(
1952 chain_id = %self.chain_id(),
1953 height = %height
1954 ))]
1955 pub(crate) async fn read_certificate(
1956 &self,
1957 height: BlockHeight,
1958 ) -> Result<Option<CacheArc<ConfirmedBlockCertificate>>, WorkerError> {
1959 let certificate_hash = match self.chain.confirmed_log.get(height.try_into()?).await? {
1960 Some(hash) => hash,
1961 None => return Ok(None),
1962 };
1963 let certificate = self
1964 .storage
1965 .read_certificate(certificate_hash)
1966 .await?
1967 .ok_or_else(|| WorkerError::ReadCertificatesError(vec![certificate_hash]))?;
1968 Ok(Some(certificate))
1969 }
1970
1971 #[instrument(skip_all, fields(
1973 chain_id = %self.chain_id(),
1974 query_application_id = %query.application_id()
1975 ))]
1976 pub(crate) async fn query_application(
1977 &mut self,
1978 query: Query,
1979 block_hash: Option<CryptoHash>,
1980 ) -> Result<(QueryOutcome, BlockHeight), WorkerError> {
1981 self.initialize_and_save_if_needed().await?;
1982 let next_block_height = self.chain.tip_state.get().next_block_height;
1983 let local_time = self.storage.clock().current_time();
1984 if let Some(requested_block) = block_hash {
1985 if let Some(mut state) = self
1986 .execution_state_cache
1987 .as_ref()
1988 .and_then(|cache| cache.remove(&requested_block))
1989 {
1990 let next_block_height = next_block_height
1993 .try_add_one()
1994 .expect("block height to not overflow");
1995 let context = QueryContext {
1996 chain_id: self.chain_id(),
1997 next_block_height,
1998 local_time,
1999 };
2000 let outcome = state
2001 .with_context(|ctx| {
2002 self.chain
2003 .execution_state
2004 .context()
2005 .clone_with_base_key(ctx.base_key().bytes.clone())
2006 })
2007 .await
2008 .query_application(context, query, self.service_runtime_endpoint.as_mut())
2009 .await
2010 .with_execution_context(ChainExecutionContext::Query)?;
2011 if let Some(cache) = &self.execution_state_cache {
2012 cache.insert(&requested_block, state);
2013 }
2014 Ok((outcome, next_block_height))
2015 } else {
2016 tracing::debug!(requested_block = %requested_block, "requested block hash not found in cache, querying committed state");
2017 let outcome = self
2018 .chain
2019 .query_application(local_time, query, self.service_runtime_endpoint.as_mut())
2020 .await?;
2021 Ok((outcome, next_block_height))
2022 }
2023 } else {
2024 let outcome = self
2025 .chain
2026 .query_application(local_time, query, self.service_runtime_endpoint.as_mut())
2027 .await?;
2028 Ok((outcome, next_block_height))
2029 }
2030 }
2031
2032 #[instrument(skip_all, fields(
2038 chain_id = %self.chain_id(),
2039 application_id = %application_id
2040 ))]
2041 pub(crate) async fn describe_application_readonly(
2042 &self,
2043 application_id: ApplicationId,
2044 ) -> Result<ApplicationDescription, WorkerError> {
2045 let blob_id = application_id.description_blob_id();
2046 let blob = self
2047 .storage
2048 .read_blob(blob_id)
2049 .await?
2050 .ok_or(WorkerError::BlobsNotFound(vec![blob_id]))?;
2051 Ok(bcs::from_bytes(blob.bytes())?)
2052 }
2053
2054 #[instrument(skip_all, fields(
2059 chain_id = %self.chain_id(),
2060 block_height = %block.height
2061 ))]
2062 pub(crate) async fn stage_block_execution(
2063 &mut self,
2064 block: ProposedBlock,
2065 round: Option<u32>,
2066 published_blobs: &[Blob],
2067 policy: BundleExecutionPolicy,
2068 ) -> Result<
2069 (
2070 ProposedBlock,
2071 Block,
2072 ChainInfoResponse,
2073 ResourceTracker,
2074 HashSet<ChainId>,
2075 ),
2076 WorkerError,
2077 > {
2078 self.initialize_and_save_if_needed().await?;
2079 let local_time = self.storage.clock().current_time();
2080 let signer = block.authenticated_signer;
2081 let (_, committee) = self.chain.current_committee().await?;
2082 block.check_proposal_size(committee.policy().maximum_block_proposal_size)?;
2083
2084 self.chain
2085 .remove_bundles_from_inboxes(block.timestamp, true, block.incoming_bundles())
2086 .await?;
2087 let (executed_block, resource_tracker, never_reject_origins) =
2088 Box::pin(self.execute_block(
2089 block,
2090 local_time,
2091 round,
2092 published_blobs,
2093 BlockExecution::StageProposal { policy },
2094 ))
2095 .await?;
2096
2097 let info = ChainInfo::from_chain_view(&self.chain).await?;
2099 let mut response = ChainInfoResponse::new(info, None);
2100 if let Some(signer) = signer {
2101 response.info.requested_owner_balance = self
2102 .chain
2103 .execution_state
2104 .system
2105 .balances
2106 .get(&signer)
2107 .await?;
2108 }
2109
2110 let (proposed_block, _) = executed_block.clone().into_proposal();
2111 Ok((
2112 proposed_block,
2113 executed_block,
2114 response,
2115 resource_tracker,
2116 never_reject_origins,
2117 ))
2118 }
2119
2120 #[instrument(skip_all, fields(
2127 chain_id = %self.chain_id(),
2128 block_height = %proposal.content.block.height
2129 ))]
2130 pub(crate) async fn handle_block_proposal(
2131 &mut self,
2132 proposal: BlockProposal,
2133 ) -> (Result<ChainInfoResponse, WorkerError>, NetworkActions) {
2134 #[cfg(with_metrics)]
2135 metrics::BLOCK_PROPOSALS_RECEIVED_TOTAL.inc();
2136 let chain_id = proposal.content.block.chain_id;
2137 let height = proposal.content.block.height;
2138 let old_round = self.chain.manager.current_round();
2139 match self.try_handle_block_proposal(proposal).await {
2140 Ok((response, actions)) => (Ok(response), actions),
2141 Err(err) => {
2142 let error_type = err.error_type();
2143 #[cfg(with_metrics)]
2144 metrics::BLOCK_PROPOSALS_REJECTED_TOTAL
2145 .with_label_values(&[error_type.as_str()])
2146 .inc();
2147 debug!(%chain_id, %height, %error_type, "Block proposal rejected");
2148 let actions = if self.chain.manager.current_round() != old_round {
2153 self.create_network_actions(Some(old_round))
2154 .await
2155 .unwrap_or_default()
2156 } else {
2157 NetworkActions::default()
2158 };
2159 (Err(err), actions)
2160 }
2161 }
2162 }
2163
2164 async fn try_handle_block_proposal(
2165 &mut self,
2166 proposal: BlockProposal,
2167 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
2168 self.initialize_and_save_if_needed().await?;
2169 proposal
2170 .check_invariants()
2171 .map_err(|msg| WorkerError::InvalidBlockProposal(msg.to_string()))?;
2172 proposal.check_signature()?;
2173 let owner = proposal.owner();
2174 let BlockProposal {
2175 content,
2176 original_proposal,
2177 signature: _,
2178 } = &proposal;
2179 let block = &content.block;
2180 let chain = &self.chain;
2181 chain.tip_state.get().verify_block_chaining(block)?;
2183 let (epoch, committee) = chain.current_committee().await?;
2185 check_block_epoch(epoch, block.chain_id, block.epoch)?;
2186 let policy = committee.policy().clone();
2187 block.check_proposal_size(policy.maximum_block_proposal_size)?;
2188 ensure!(
2190 chain.manager.verify_owner(&owner, proposal.content.round)?,
2191 WorkerError::InvalidOwner
2192 );
2193 let old_round = self.chain.manager.current_round();
2194 match original_proposal {
2195 None => {
2196 if let Some(signer) = block.authenticated_signer {
2197 ensure!(signer == owner, WorkerError::InvalidSigner(owner));
2199 }
2200 }
2201 Some(OriginalProposal::Regular { certificate }) => {
2202 certificate.check(&committee)?;
2204 }
2205 Some(OriginalProposal::Fast(signature)) => {
2206 let original_proposal = BlockProposal {
2207 content: ProposalContent {
2208 block: content.block.clone(),
2209 round: Round::Fast,
2210 outcome: None,
2211 },
2212 signature: *signature,
2213 original_proposal: None,
2214 };
2215 let super_owner = original_proposal.owner();
2216 ensure!(
2217 chain
2218 .manager
2219 .ownership
2220 .get()
2221 .super_owners
2222 .contains(&super_owner),
2223 WorkerError::InvalidOwner
2224 );
2225 if let Some(signer) = block.authenticated_signer {
2226 ensure!(signer == super_owner, WorkerError::InvalidSigner(signer));
2228 }
2229 original_proposal.check_signature()?;
2230 }
2231 }
2232 let local_time = self.storage.clock().current_time();
2233 match chain.manager.check_proposed_block(&proposal) {
2234 Ok(manager::Outcome::Skip) => {
2235 return Ok((self.chain_info_response().await?, NetworkActions::default()));
2237 }
2238 Ok(manager::Outcome::Accept) => {}
2239 Err(err) => {
2240 if matches!(err, ChainError::HasIncompatibleConfirmedVote(_, _))
2248 && self
2249 .chain
2250 .manager
2251 .update_signed_proposal(&proposal, local_time)
2252 {
2253 self.save().await?;
2254 }
2255 return Err(err.into());
2256 }
2257 }
2258
2259 if self
2262 .chain
2263 .manager
2264 .update_signed_proposal(&proposal, local_time)
2265 {
2266 self.save().await?;
2267 }
2268
2269 let published_blobs = self.load_proposal_blobs(&proposal).await?;
2270 let ProposalContent {
2271 block,
2272 round,
2273 outcome,
2274 } = content;
2275
2276 if self.config.key_pair().is_some()
2277 && block.timestamp.duration_since(local_time) > self.config.block_time_grace_period
2278 {
2279 return Err(WorkerError::InvalidTimestamp {
2280 local_time,
2281 block_timestamp: block.timestamp,
2282 block_time_grace_period: self.config.block_time_grace_period,
2283 });
2284 }
2285 self.chain
2290 .remove_bundles_from_inboxes(block.timestamp, true, block.incoming_bundles())
2291 .await?;
2292 let block = if let Some(outcome) = outcome {
2293 outcome.clone().with(proposal.content.block.clone())
2294 } else {
2295 let (executed_block, _resource_tracker, _) = Box::pin(self.execute_block(
2296 block.clone(),
2297 local_time,
2298 round.multi_leader(),
2299 &published_blobs,
2300 BlockExecution::HandleProposal,
2301 ))
2302 .await?;
2303 executed_block
2304 };
2305
2306 ensure!(
2307 !round.is_fast() || !block.has_oracle_responses(),
2308 WorkerError::FastBlockUsingOracles
2309 );
2310 let chain = &mut self.chain;
2311 chain
2313 .tip_state
2314 .get_mut()
2315 .update_counters(&block.body.transactions, &block.body.messages)?;
2316 chain.rollback();
2318
2319 let blobs = self
2321 .get_required_blobs(proposal.expected_blob_ids(), block.created_blobs())
2322 .await?;
2323 let key_pair = self.config.key_pair();
2324 let manager = &mut self.chain.manager;
2325 match manager.create_vote(&proposal, block, key_pair, local_time, blobs)? {
2326 Some(Either::Left(vote)) => {
2328 self.block_values
2329 .insert_hashed(Cow::Borrowed(vote.value.inner()));
2330 }
2331 Some(Either::Right(vote)) => {
2332 self.block_values
2333 .insert_hashed(Cow::Borrowed(vote.value.inner()));
2334 }
2335 None => (),
2336 }
2337 self.save().await?;
2338 let actions = self.create_network_actions(Some(old_round)).await?;
2339 Ok((self.chain_info_response().await?, actions))
2340 }
2341
2342 #[instrument(skip_all, fields(
2344 chain_id = %self.chain_id()
2345 ))]
2346 async fn prepare_chain_info_response(
2347 &mut self,
2348 query: ChainInfoQuery,
2349 ) -> Result<ChainInfoResponse, WorkerError> {
2350 self.initialize_and_save_if_needed().await?;
2351 let mut info = ChainInfo::from_chain_view(&self.chain).await?;
2352 if query.request_committees {
2353 info.requested_committees = Some(
2360 self.chain
2361 .execution_state
2362 .system
2363 .committees
2364 .get()
2365 .await?
2366 .clone(),
2367 );
2368 }
2369 if query.request_owner_balance == AccountOwner::CHAIN {
2370 info.requested_owner_balance = Some(*self.chain.execution_state.system.balance.get());
2371 } else {
2372 info.requested_owner_balance = self
2373 .chain
2374 .execution_state
2375 .system
2376 .balances
2377 .get(&query.request_owner_balance)
2378 .await?;
2379 }
2380 if let Some(next_block_height) = query.test_next_block_height {
2381 ensure!(
2383 self.chain.tip_state.get().next_block_height == next_block_height,
2384 WorkerError::UnexpectedBlockHeight {
2385 expected_block_height: self.chain.tip_state.get().next_block_height,
2386 found_block_height: next_block_height,
2387 }
2388 );
2389 }
2390 if query.request_pending_message_bundles {
2391 let origins = if let Some(nonempty_origins) = self.chain.nonempty_inboxes.get().clone()
2393 {
2394 nonempty_origins.into_iter().collect::<Vec<_>>()
2395 } else {
2396 let pairs = self.chain.inboxes.try_load_all_entries().await?;
2397 let nonempty_origins = pairs
2398 .into_iter()
2399 .filter(|(_, inbox)| inbox.added_bundles.count() > 0)
2400 .map(|(origin, _)| origin)
2401 .collect::<BTreeSet<ChainId>>();
2402 let origins = nonempty_origins.iter().copied().collect::<Vec<_>>();
2403 *self.chain.nonempty_inboxes.get_mut() = Some(nonempty_origins);
2404 self.save().await?;
2405 origins
2406 };
2407 let mut bundles = Vec::new();
2408 let inboxes = self.chain.inboxes.try_load_entries(&origins).await?;
2409 let origins_and_inboxes = origins
2410 .into_iter()
2411 .zip(inboxes)
2412 .filter_map(|(origin, inbox)| Some((origin, inbox?)))
2413 .collect::<Vec<_>>();
2414 #[cfg(with_metrics)]
2415 metrics::NUM_INBOXES
2416 .with_label_values(&[])
2417 .observe(origins_and_inboxes.len() as f64);
2418 let is_closed = *self.chain.execution_state.system.closed.get();
2419 let action = if is_closed {
2420 MessageAction::Reject
2421 } else {
2422 MessageAction::Accept
2423 };
2424 for (origin, inbox) in origins_and_inboxes {
2425 for bundle in inbox.added_bundles.elements().await? {
2426 bundles.push(IncomingBundle {
2427 origin,
2428 bundle,
2429 action,
2430 });
2431 }
2432 }
2433 if is_closed && !bundles.is_empty() {
2434 info!(
2435 chain_id = %self.chain.chain_id(),
2436 count = bundles.len(),
2437 "Auto-rejecting all incoming message bundles because the chain is closed"
2438 );
2439 }
2440 info.requested_pending_message_bundles = bundles;
2441 }
2442 let hashes = self
2443 .chain
2444 .block_hashes(query.request_sent_certificate_hashes_by_heights)
2445 .await?;
2446 info.requested_sent_certificate_hashes = hashes;
2447 if let Some(start) = query.request_received_log_excluding_first_n {
2448 let start = usize::try_from(start).map_err(|_| ArithmeticError::Overflow)?;
2449 let max_received_log_entries = self.config.chain_info_max_received_log_entries;
2450 let end = start
2451 .saturating_add(max_received_log_entries)
2452 .min(self.chain.received_log.count());
2453 info.requested_received_log = self.chain.received_log.read(start..end).await?;
2454 }
2455 if query.request_manager_values {
2456 info.manager.add_values(&self.chain.manager);
2457 }
2458 Ok(ChainInfoResponse::new(info, self.config.key_pair()))
2459 }
2460
2461 #[instrument(skip_all, fields(
2465 chain_id = %self.chain_id(),
2466 block_height = %block.height
2467 ))]
2468 async fn execute_block(
2469 &mut self,
2470 block: ProposedBlock,
2471 local_time: Timestamp,
2472 round: Option<u32>,
2473 published_blobs: &[Blob],
2474 execution: BlockExecution,
2475 ) -> Result<(Block, ResourceTracker, HashSet<ChainId>), WorkerError> {
2476 let (proposed_block, outcome, resource_tracker, never_reject_origins) = Box::pin(
2477 self.chain
2478 .execute_block(block, local_time, round, published_blobs, execution),
2479 )
2480 .await?;
2481 let executed_block = Block::new(proposed_block, outcome);
2482 let block_hash = CryptoHash::new(&executed_block);
2483 if let Some(cache) = &self.execution_state_cache {
2484 cache.insert(
2485 &block_hash,
2486 Box::pin(
2487 self.chain
2488 .execution_state
2489 .with_context(|ctx| InactiveContext(ctx.base_key().clone())),
2490 )
2491 .await,
2492 );
2493 }
2494 Ok((executed_block, resource_tracker, never_reject_origins))
2495 }
2496
2497 #[instrument(skip_all, fields(
2499 chain_id = %self.chain_id()
2500 ))]
2501 pub(crate) async fn initialize_and_save_if_needed(&mut self) -> Result<(), WorkerError> {
2502 if !self.knows_chain_is_active {
2503 let local_time = self.storage.clock().current_time();
2504 self.chain.initialize_if_needed(local_time).await?;
2505 self.save().await?;
2506 self.knows_chain_is_active = true;
2507 }
2508 Ok(())
2509 }
2510
2511 pub(crate) async fn chain_info_response(&self) -> Result<ChainInfoResponse, WorkerError> {
2512 let info = ChainInfo::from_chain_view(&self.chain).await?;
2513 Ok(ChainInfoResponse::new(info, self.config.key_pair()))
2514 }
2515
2516 #[instrument(skip_all, fields(
2520 chain_id = %self.chain_id()
2521 ))]
2522 pub(crate) async fn save(&mut self) -> Result<(), WorkerError> {
2523 if let Err(error) = self.chain.save().await {
2524 if error.must_reload_view() {
2525 tracing::error!(
2526 ?error,
2527 chain_id = %self.chain_id(),
2528 "Chain save failed with a nonrecoverable error; marking worker as poisoned"
2529 );
2530 self.poisoned = true;
2531 }
2532 return Err(WorkerError::ViewError(error));
2533 }
2534 self.chain.execution_state.system.committees.evict();
2537 Ok(())
2538 }
2539}
2540
2541fn classify_processing_error(error: WorkerError) -> (Option<WorkerError>, WorkerError) {
2549 if error.must_reload_view() || error.indicates_corrupted_chain_state() {
2550 (Some(error), WorkerError::BatchRolledBack)
2551 } else {
2552 (None, error)
2553 }
2554}
2555
2556pub(crate) fn send_result<T>(sender: oneshot::Sender<T>, value: T) {
2559 if sender.send(value).is_err() {
2560 tracing::debug!("cannot send cross-chain result; receiver dropped");
2561 }
2562}
2563
2564fn missing_indices_blob_ids(maybe_blobs: &[(BlobId, Option<Blob>)]) -> (Vec<usize>, Vec<BlobId>) {
2566 let mut missing_indices = Vec::new();
2567 let mut missing_blob_ids = Vec::new();
2568 for (index, (blob_id, blob)) in maybe_blobs.iter().enumerate() {
2569 if blob.is_none() {
2570 missing_indices.push(index);
2571 missing_blob_ids.push(*blob_id);
2572 }
2573 }
2574 (missing_indices, missing_blob_ids)
2575}
2576
2577fn missing_blob_ids<'a>(
2579 maybe_blobs: impl IntoIterator<Item = (&'a BlobId, &'a Option<Blob>)>,
2580) -> Vec<BlobId> {
2581 maybe_blobs
2582 .into_iter()
2583 .filter(|(_, maybe_blob)| maybe_blob.is_none())
2584 .map(|(blob_id, _)| *blob_id)
2585 .collect()
2586}
2587
2588fn check_block_epoch(
2590 chain_epoch: Epoch,
2591 block_chain: ChainId,
2592 block_epoch: Epoch,
2593) -> Result<(), WorkerError> {
2594 ensure!(
2595 block_epoch == chain_epoch,
2596 WorkerError::InvalidEpoch {
2597 chain_id: block_chain,
2598 epoch: block_epoch,
2599 chain_epoch
2600 }
2601 );
2602 Ok(())
2603}
2604
2605pub(crate) struct CrossChainUpdateHelper {
2607 pub(crate) allow_messages_from_deprecated_epochs: bool,
2608 pub(crate) current_epoch: Epoch,
2609}
2610
2611impl CrossChainUpdateHelper {
2612 fn new<C>(config: &ChainWorkerConfig, chain: &ChainStateView<C>) -> Self
2614 where
2615 C: Context + Clone + 'static,
2616 {
2617 CrossChainUpdateHelper {
2618 allow_messages_from_deprecated_epochs: config.allow_messages_from_deprecated_epochs,
2619 current_epoch: *chain.execution_state.system.epoch.get(),
2620 }
2621 }
2622
2623 pub(crate) async fn select_message_bundles<S: Storage>(
2633 &self,
2634 origin: &ChainId,
2635 recipient: ChainId,
2636 next_height_to_receive: BlockHeight,
2637 last_anticipated_block_height: Option<BlockHeight>,
2638 mut bundles: Vec<(Epoch, MessageBundle)>,
2639 storage: &S,
2640 ) -> Result<Vec<MessageBundle>, WorkerError> {
2641 let mut latest_height = None;
2642 let mut skipped_len = 0;
2643 let mut trusted_len = 0;
2644 for (i, (epoch, bundle)) in bundles.iter().enumerate() {
2645 ensure!(
2647 latest_height <= Some(bundle.height),
2648 WorkerError::InvalidCrossChainRequest
2649 );
2650 latest_height = Some(bundle.height);
2651 if bundle.height < next_height_to_receive {
2653 skipped_len = i + 1;
2654 }
2655 let epoch_is_known = self.allow_messages_from_deprecated_epochs
2659 || Some(bundle.height) <= last_anticipated_block_height
2660 || *epoch >= self.current_epoch
2661 || storage.get_or_load_committee(*epoch).await?.is_some();
2662 if epoch_is_known {
2663 trusted_len = i + 1;
2664 }
2665 }
2666 if skipped_len > 0 {
2667 let (_, sample_bundle) = &bundles[skipped_len - 1];
2668 debug!(
2669 "Ignoring repeated messages to {recipient:.8} from {origin:} at height {}",
2670 sample_bundle.height,
2671 );
2672 }
2673 if skipped_len < bundles.len() && trusted_len < bundles.len() {
2674 let (sample_epoch, sample_bundle) = &bundles[trusted_len];
2675 warn!(
2676 "Refusing messages to {recipient:.8} from {origin:} at height {} \
2677 because the epoch {} is not known locally",
2678 sample_bundle.height, sample_epoch,
2679 );
2680 }
2681 let bundles = if skipped_len < trusted_len {
2682 bundles
2683 .drain(skipped_len..trusted_len)
2684 .map(|(_, bundle)| bundle)
2685 .collect()
2686 } else {
2687 vec![]
2688 };
2689 Ok(bundles)
2690 }
2691}