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 BlockExecutionPhase, ChainError, ChainExecutionContext, ChainIdSet, ChainStateView,
37 ChainTipState, 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::{handle::AtomicTimestamp, ChainWorkerConfig, DeliveryNotifier},
56 client::{ChainModes, ListeningMode},
57 data_types::{ChainInfo, ChainInfoQuery, ChainInfoResponse, CrossChainRequest},
58 worker::{BatchRequest, NetworkActions, Notification, Reason, WorkerError},
59};
60
61pub(crate) type EventSubscriptionsResult = Vec<((ChainId, StreamId), EventSubscriptions)>;
63
64#[cfg(with_metrics)]
65mod metrics {
66 use std::sync::LazyLock;
67
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 pub static CREATE_NETWORK_ACTIONS_LATENCY: LazyLock<Histogram> = LazyLock::new(|| {
75 register_histogram(
76 "create_network_actions_latency",
77 "Time (ms) to create network actions",
78 exponential_bucket_latencies(10_000.0),
79 )
80 });
81
82 pub static NUM_INBOXES: LazyLock<HistogramVec> = LazyLock::new(|| {
83 register_histogram_vec(
84 "num_inboxes",
85 "Number of inboxes",
86 &[],
87 exponential_bucket_interval(1.0, 10_000.0),
88 )
89 });
90
91 pub static BLOCK_PROPOSALS_RECEIVED_TOTAL: LazyLock<IntCounter> = LazyLock::new(|| {
92 register_int_counter(
93 "block_proposals_received_total",
94 "Total number of block proposals received by the worker",
95 )
96 });
97
98 pub static BLOCK_PROPOSALS_REJECTED_TOTAL: LazyLock<IntCounterVec> = LazyLock::new(|| {
99 register_int_counter_vec(
100 "block_proposals_rejected_total",
101 "Total number of block proposals rejected by the worker, labelled by error type",
102 &["error_type"],
103 )
104 });
105}
106
107pub(crate) struct ChainWorkerState<StorageClient>
109where
110 StorageClient: Storage,
111{
112 config: ChainWorkerConfig,
113 storage: StorageClient,
114 chain: ChainStateView<StorageClient::Context>,
115 service_runtime_endpoint: Option<ServiceRuntimeEndpoint>,
116 service_runtime_task: Option<web_thread_pool::Task<()>>,
121 last_access: Arc<AtomicTimestamp>,
126 block_values: Arc<ValueCache<CryptoHash, ConfirmedBlock>>,
127 execution_state_cache:
128 Option<Arc<UniqueValueCache<CryptoHash, ExecutionStateView<InactiveContext>>>>,
129 chain_modes: Option<Arc<sync::RwLock<ChainModes>>>,
130 delivery_notifier: DeliveryNotifier,
131 knows_chain_is_active: bool,
132 poisoned: bool,
135}
136
137pub(crate) enum CrossChainUpdateResult {
139 Updated(BlockHeight),
141 NothingToDo,
143 GapDetected {
147 origin: ChainId,
148 retransmit_from: BlockHeight,
149 },
150}
151
152pub enum BlockOutcome {
154 Processed,
155 Preprocessed,
156 Skipped,
157}
158
159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
161pub enum ProcessConfirmedBlockMode {
162 Auto,
166 Execute,
170 Preprocess,
174}
175
176impl<StorageClient> ChainWorkerState<StorageClient>
177where
178 StorageClient: Storage + Clone + 'static,
179{
180 #[instrument(skip_all, fields(
182 chain_id = %chain_id
183 ))]
184 #[expect(clippy::too_many_arguments)]
185 pub(crate) async fn load(
186 config: ChainWorkerConfig,
187 storage: StorageClient,
188 block_values: Arc<ValueCache<CryptoHash, ConfirmedBlock>>,
189 execution_state_cache: Option<
190 Arc<UniqueValueCache<CryptoHash, ExecutionStateView<InactiveContext>>>,
191 >,
192 chain_modes: Option<Arc<sync::RwLock<ChainModes>>>,
193 delivery_notifier: DeliveryNotifier,
194 chain_id: ChainId,
195 service_runtime_endpoint: Option<ServiceRuntimeEndpoint>,
196 service_runtime_task: Option<web_thread_pool::Task<()>>,
197 ) -> Result<Self, WorkerError> {
198 let chain = storage.load_chain(chain_id).await?;
199
200 Ok(ChainWorkerState {
201 config,
202 storage,
203 chain,
204 service_runtime_endpoint,
205 service_runtime_task,
206 last_access: Arc::new(AtomicTimestamp::now()),
207 block_values,
208 execution_state_cache,
209 chain_modes,
210 delivery_notifier,
211 knows_chain_is_active: false,
212 poisoned: false,
213 })
214 }
215
216 fn chain_id(&self) -> ChainId {
218 self.chain.chain_id()
219 }
220
221 pub(crate) fn chain(&self) -> &ChainStateView<StorageClient::Context> {
223 &self.chain
224 }
225
226 async fn committee_for_epoch(
233 &self,
234 epoch: Epoch,
235 ) -> Result<linera_execution::committee::Committee, WorkerError> {
236 let hash = self
237 .chain
238 .execution_state
239 .context()
240 .extra()
241 .get_committee_hashes(epoch..=epoch)
242 .await
243 .map_err(|error| {
244 ChainError::ExecutionError(Box::new(error), ChainExecutionContext::Block)
245 })?
246 .remove(&epoch)
247 .ok_or_else(|| {
248 ChainError::InternalError(format!(
249 "missing committee for epoch {epoch}; this is a bug"
250 ))
251 })?;
252 let committee = self
253 .chain
254 .execution_state
255 .context()
256 .extra()
257 .get_or_load_committee_by_hash(hash)
258 .await
259 .map_err(|error| {
260 ChainError::ExecutionError(Box::new(error), ChainExecutionContext::Block)
261 })?;
262 Ok((*committee).clone())
263 }
264
265 pub(crate) async fn select_message_bundles(
273 &self,
274 origin: &ChainId,
275 next_height_to_receive: BlockHeight,
276 last_anticipated_block_height: Option<BlockHeight>,
277 mut bundles: Vec<(Epoch, MessageBundle)>,
278 ) -> Result<Vec<MessageBundle>, WorkerError> {
279 let recipient = self.chain_id();
280 let mut latest_height = None;
281 let mut skipped_len = 0;
282 let mut trusted_len = 0;
283 for (i, (epoch, bundle)) in bundles.iter().enumerate() {
284 ensure!(
285 latest_height <= Some(bundle.height),
286 WorkerError::InvalidCrossChainRequest
287 );
288 latest_height = Some(bundle.height);
289 if bundle.height < next_height_to_receive {
290 skipped_len = i + 1;
291 }
292 let is_revoked = self
293 .storage
294 .is_epoch_revoked(*epoch)
295 .await
296 .map_err(|error| {
297 WorkerError::ChainError(Box::new(ChainError::ExecutionError(
298 Box::new(error),
299 ChainExecutionContext::Block,
300 )))
301 })?;
302 if !is_revoked || Some(bundle.height) <= last_anticipated_block_height {
303 trusted_len = i + 1;
304 }
305 }
306 if skipped_len > 0 {
307 let (_, sample_bundle) = &bundles[skipped_len - 1];
308 debug!(
309 "Ignoring repeated messages to {recipient:.8} from {origin:} at height {}",
310 sample_bundle.height,
311 );
312 }
313 if skipped_len < bundles.len() && trusted_len < bundles.len() {
314 let (sample_epoch, sample_bundle) = &bundles[trusted_len];
315 warn!(
316 "Refusing messages to {recipient:.8} from {origin:} at height {} \
317 because the epoch {} is not trusted any more",
318 sample_bundle.height, sample_epoch,
319 );
320 }
321 Ok(if skipped_len < trusted_len {
322 bundles
323 .drain(skipped_len..trusted_len)
324 .map(|(_, bundle)| bundle)
325 .collect()
326 } else {
327 vec![]
328 })
329 }
330
331 pub(crate) fn knows_chain_is_active(&self) -> bool {
333 self.knows_chain_is_active
334 }
335
336 pub(crate) fn rollback(&mut self) {
338 self.chain.rollback();
339 }
340
341 pub(crate) fn check_not_poisoned(&self) -> Result<(), WorkerError> {
344 ensure!(!self.poisoned, WorkerError::PoisonedWorker);
345 Ok(())
346 }
347
348 pub(crate) fn touch(&self) {
350 self.last_access.store_now();
351 }
352
353 pub(crate) fn last_access_arc(&self) -> Arc<AtomicTimestamp> {
355 Arc::clone(&self.last_access)
356 }
357
358 pub(crate) fn clear_service_runtime(&mut self) -> Option<web_thread_pool::Task<()>> {
361 self.service_runtime_endpoint.take();
362 self.service_runtime_task.take()
363 }
364
365 pub(crate) async fn cross_chain_network_actions_if_reconciled(
369 &self,
370 ) -> Result<Option<NetworkActions>, WorkerError> {
371 let tracked = self.tracked_full_chains();
372 if !self.chain.outbox_index_is_reconciled(tracked.as_deref()) {
373 return Ok(None);
374 }
375 Ok(Some(
376 self.build_network_actions(None, tracked.as_deref().map(|h| h.inner()))
377 .await?,
378 ))
379 }
380
381 #[instrument(skip_all, fields(chain_id = %self.chain_id()))]
388 pub(crate) async fn reconcile_and_cross_chain_network_actions(
389 &mut self,
390 ) -> Result<NetworkActions, WorkerError> {
391 let tracked = self.tracked_full_chains();
392 self.chain
393 .reconcile_outbox_index(tracked.as_deref())
394 .await?;
395 let actions = self
396 .build_network_actions(None, tracked.as_deref().map(|h| h.inner()))
397 .await?;
398 self.save().await?;
399 Ok(actions)
400 }
401
402 #[tracing::instrument(level = "debug", skip(self))]
404 pub(crate) async fn handle_chain_info_query(
405 &mut self,
406 query: ChainInfoQuery,
407 ) -> Result<ChainInfoResponse, WorkerError> {
408 if let Some((height, round)) = query.request_leader_timeout {
409 self.vote_for_leader_timeout(height, round).await?;
410 }
411 if query.request_fallback {
412 self.vote_for_fallback().await?;
413 }
414 self.prepare_chain_info_response(query).await
415 }
416
417 #[instrument(skip_all, fields(
419 chain_id = %self.chain_id(),
420 blob_id = %blob_id
421 ))]
422 pub(crate) async fn download_pending_blob(
423 &self,
424 blob_id: BlobId,
425 ) -> Result<CacheArc<Blob>, WorkerError> {
426 if let Some(blob) = self.chain.manager.pending_blob(&blob_id).await? {
427 return Ok(self.storage.cache_blob(blob));
428 }
429 self.storage
430 .read_blob(blob_id)
431 .await?
432 .ok_or(WorkerError::BlobsNotFound(vec![blob_id]))
433 }
434
435 #[instrument(skip_all, fields(
438 chain_id = %self.chain_id()
439 ))]
440 async fn get_required_blobs(
441 &self,
442 required_blob_ids: impl IntoIterator<Item = BlobId>,
443 created_blobs: BTreeMap<BlobId, Blob>,
444 ) -> Result<BTreeMap<BlobId, Blob>, WorkerError> {
445 let maybe_blobs = self
446 .maybe_get_required_blobs(required_blob_ids, Some(created_blobs))
447 .await?;
448 let not_found_blob_ids = missing_blob_ids(&maybe_blobs);
449 ensure!(
450 not_found_blob_ids.is_empty(),
451 WorkerError::BlobsNotFound(not_found_blob_ids)
452 );
453 Ok(maybe_blobs
454 .into_iter()
455 .filter_map(|(blob_id, maybe_blob)| Some((blob_id, maybe_blob?)))
456 .collect())
457 }
458
459 #[instrument(skip_all, fields(
461 chain_id = %self.chain_id()
462 ))]
463 async fn maybe_get_required_blobs(
464 &self,
465 blob_ids: impl IntoIterator<Item = BlobId>,
466 mut created_blobs: Option<BTreeMap<BlobId, Blob>>,
467 ) -> Result<BTreeMap<BlobId, Option<Blob>>, WorkerError> {
468 let maybe_blobs = blob_ids.into_iter().collect::<BTreeSet<_>>();
469 let mut maybe_blobs = maybe_blobs
470 .into_iter()
471 .map(|x| (x, None))
472 .collect::<Vec<(BlobId, Option<Blob>)>>();
473
474 if let Some(blob_map) = &mut created_blobs {
475 for (blob_id, value) in &mut maybe_blobs {
476 if let Some(blob) = blob_map.remove(blob_id) {
477 *value = Some(blob);
478 }
479 }
480 }
481
482 let (missing_indices, missing_blob_ids) = missing_indices_blob_ids(&maybe_blobs);
483 let second_block_blobs = self.chain.manager.pending_blobs(&missing_blob_ids).await?;
484 for (index, blob) in missing_indices.into_iter().zip(second_block_blobs) {
485 maybe_blobs[index].1 = blob;
486 }
487
488 let (missing_indices, missing_blob_ids) = missing_indices_blob_ids(&maybe_blobs);
489 let third_block_blobs = self
490 .chain
491 .pending_validated_blobs
492 .multi_get(&missing_blob_ids)
493 .await?;
494 for (index, blob) in missing_indices.into_iter().zip(third_block_blobs) {
495 maybe_blobs[index].1 = blob;
496 }
497
498 let (missing_indices, missing_blob_ids) = missing_indices_blob_ids(&maybe_blobs);
499 if !missing_indices.is_empty() {
500 let all_entries_pending_blobs = self
501 .chain
502 .pending_proposed_blobs
503 .try_load_all_entries()
504 .await?;
505 for (index, blob_id) in missing_indices.into_iter().zip(missing_blob_ids) {
506 for (_, pending_blobs) in &all_entries_pending_blobs {
507 if let Some(blob) = pending_blobs.get(&blob_id).await? {
508 maybe_blobs[index].1 = Some(blob);
509 break;
510 }
511 }
512 }
513 }
514
515 let (missing_indices, missing_blob_ids) = missing_indices_blob_ids(&maybe_blobs);
516 let fourth_block_blobs = self.storage.read_blobs(&missing_blob_ids).await?;
517 for (index, blob) in missing_indices.into_iter().zip(fourth_block_blobs) {
518 maybe_blobs[index].1 = blob.map(CacheArc::unwrap_or_clone);
519 }
520 Ok(maybe_blobs.into_iter().collect())
521 }
522
523 #[instrument(skip_all, fields(
525 chain_id = %self.chain_id()
526 ))]
527 async fn create_cross_chain_actions_for_recipient(
528 &self,
529 recipient: ChainId,
530 ) -> Result<NetworkActions, WorkerError> {
531 let outbox = self.chain.outboxes.try_load_entry(&recipient).await?;
532 let Some(outbox) = outbox else {
533 return Ok(NetworkActions::default());
534 };
535 let heights = outbox.queue.elements().await?;
536 if heights.is_empty() {
537 return Ok(NetworkActions::default());
538 }
539 let heights_by_recipient = BTreeMap::from([(recipient, heights)]);
540 let cross_chain_requests = self
541 .create_cross_chain_requests(heights_by_recipient)
542 .await?;
543 Ok(NetworkActions {
544 cross_chain_requests,
545 notifications: Vec::new(),
546 })
547 }
548
549 fn tracked_full_chains(&self) -> Option<Arc<Hashed<ChainIdSet>>> {
552 let chain_modes = self.chain_modes.as_ref()?;
553 let full = chain_modes
554 .read()
555 .expect("Panics should not happen while holding a lock to `chain_modes`")
556 .full();
557 Some(full)
558 }
559
560 fn is_tracked(&self, chain_id: &ChainId) -> bool {
563 self.chain_modes.as_ref().is_none_or(|chain_modes| {
564 chain_modes
565 .read()
566 .expect("Panics should not happen while holding a lock to `chain_modes`")
567 .get(chain_id)
568 .is_some_and(ListeningMode::is_full)
569 })
570 }
571
572 async fn reconcile_tracked_outboxes(
575 &mut self,
576 ) -> Result<Option<Arc<Hashed<ChainIdSet>>>, WorkerError> {
577 let full_chains = self.tracked_full_chains();
578 self.chain
579 .reconcile_outbox_index(full_chains.as_deref())
580 .await?;
581 Ok(full_chains)
582 }
583
584 async fn create_network_actions(
587 &mut self,
588 old_round: Option<Round>,
589 ) -> Result<NetworkActions, WorkerError> {
590 let tracked = self.reconcile_tracked_outboxes().await?;
593 self.build_network_actions(old_round, tracked.as_deref().map(|h| h.inner()))
594 .await
595 }
596
597 async fn build_network_actions(
599 &self,
600 old_round: Option<Round>,
601 tracked: Option<&ChainIdSet>,
602 ) -> Result<NetworkActions, WorkerError> {
603 #[cfg(with_metrics)]
604 let _latency = metrics::CREATE_NETWORK_ACTIONS_LATENCY.measure_latency();
605 let mut heights_by_recipient = BTreeMap::<_, Vec<_>>::new();
606 let targets = self.chain.nonempty_outbox_chain_ids();
607 if let Some(tracked) = tracked {
608 if let Some(target) = targets.iter().find(|target| !tracked.contains(*target)) {
609 return Err(ChainError::CorruptedChainState(format!(
610 "outbox index contains untracked target {target}"
611 ))
612 .into());
613 }
614 }
615 let outboxes = self.chain.load_outboxes(&targets).await?;
616 for (target, outbox) in targets.into_iter().zip(outboxes) {
617 let heights = outbox.queue.elements().await?;
618 heights_by_recipient.insert(target, heights);
619 }
620 let cross_chain_requests = self
621 .create_cross_chain_requests(heights_by_recipient)
622 .await?;
623 let mut notifications = Vec::new();
624 if let Some(old_round) = old_round {
625 let round = self.chain.manager.current_round();
626 if round > old_round {
627 let height = self.chain.tip_state.get().next_block_height;
628 notifications.push(Notification {
629 chain_id: self.chain_id(),
630 reason: Reason::NewRound { height, round },
631 });
632 }
633 }
634 Ok(NetworkActions {
635 cross_chain_requests,
636 notifications,
637 })
638 }
639
640 async fn read_confirmed_blocks(
643 &self,
644 hashes: &[CryptoHash],
645 ) -> Result<Vec<Option<CacheArc<ConfirmedBlock>>>, WorkerError> {
646 let mut blocks = Vec::with_capacity(hashes.len());
647 let mut uncached_indices = Vec::new();
648 let mut uncached_hashes = Vec::new();
649
650 for (i, hash) in hashes.iter().enumerate() {
651 if let Some(block) = self.block_values.get(hash) {
652 blocks.push(Some(block));
653 } else {
654 blocks.push(None);
655 uncached_indices.push(i);
656 uncached_hashes.push(*hash);
657 }
658 }
659
660 if !uncached_hashes.is_empty() {
661 let from_storage = self.storage.read_confirmed_blocks(uncached_hashes).await?;
662 for (i, maybe_block) in uncached_indices.into_iter().zip(from_storage) {
663 blocks[i] = maybe_block;
664 }
665 }
666
667 Ok(blocks)
668 }
669
670 #[instrument(skip_all, fields(
671 chain_id = %self.chain_id(),
672 num_recipients = %heights_by_recipient.len()
673 ))]
674 async fn create_cross_chain_requests(
675 &self,
676 heights_by_recipient: BTreeMap<ChainId, Vec<BlockHeight>>,
677 ) -> Result<Vec<CrossChainRequest>, WorkerError> {
678 let heights = heights_by_recipient
680 .values()
681 .flatten()
682 .copied()
683 .collect::<BTreeSet<_>>();
684 let hashes = self
685 .chain
686 .block_hashes_for_heights(heights.iter().copied())
687 .await?;
688
689 let blocks = self.read_confirmed_blocks(&hashes).await?;
690
691 let mut height_to_blocks = HashMap::new();
692 for (block, hash) in blocks.into_iter().zip(hashes) {
693 let block = block.ok_or_else(|| WorkerError::ReadCertificatesError(vec![hash]))?;
694 height_to_blocks.insert(block.height(), block);
695 }
696
697 let sender = self.chain.chain_id();
698 let mut cross_chain_requests = Vec::new();
699 for (recipient, heights) in heights_by_recipient {
700 let previous_height = heights.first().and_then(|first_height| {
704 let block = height_to_blocks.get(first_height)?;
705 let (_, prev_height) =
706 block.block().body.previous_message_blocks.get(&recipient)?;
707 Some(*prev_height)
708 });
709 let mut bundles = Vec::new();
710 let mut bundles_size = 0;
711 for height in heights {
712 let Some(confirmed_block) = height_to_blocks.get(&height) else {
713 tracing::warn!(
714 %height,
715 %recipient,
716 "spurious entry in outbox; skipping this and higher sender blocks"
717 );
718 break;
719 };
720 let new_bundles = confirmed_block
721 .block()
722 .message_bundles_for(recipient, confirmed_block.inner().hash())
723 .collect::<Vec<_>>();
724 let new_size = new_bundles
725 .iter()
726 .map(|(_epoch, bundle)| bundle.estimated_size())
727 .sum::<usize>();
728 if bundles_size + new_size > self.config.cross_chain_message_chunk_limit {
731 if bundles.is_empty() {
732 warn!(
733 "Single block at height {height} produces an UpdateRecipient \
734 of ~{new_size} bytes, exceeding the chunk limit of {}",
735 self.config.cross_chain_message_chunk_limit
736 );
737 } else {
738 debug!(
739 "Stopping cross-chain batch for {recipient} at height {height}: \
740 adding ~{new_size} bytes would exceed chunk limit of {} \
741 (current batch ~{bundles_size} bytes)",
742 self.config.cross_chain_message_chunk_limit
743 );
744 break;
745 }
746 }
747 bundles.extend(new_bundles);
748 bundles_size += new_size;
749 }
750 if !bundles.is_empty() {
751 cross_chain_requests.push(CrossChainRequest::UpdateRecipient {
752 sender,
753 recipient,
754 bundles,
755 previous_height,
756 });
757 }
758 }
759 Ok(cross_chain_requests)
760 }
761
762 #[instrument(skip_all, fields(
764 chain_id = %self.chain_id(),
765 height = %certificate.inner().height()
766 ))]
767 pub(crate) async fn process_timeout(
768 &mut self,
769 certificate: TimeoutCertificate,
770 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
771 self.initialize_and_save_if_needed().await?;
774 let (chain_epoch, committee) = self.chain.current_committee().await?;
775 certificate.check(&committee)?;
776 if self
777 .chain
778 .tip_state
779 .get()
780 .already_validated_block(certificate.inner().height())?
781 {
782 return Ok((self.chain_info_response().await?, NetworkActions::default()));
783 }
784 ensure!(
785 certificate.inner().epoch() == chain_epoch,
786 WorkerError::InvalidEpoch {
787 chain_id: certificate.inner().chain_id(),
788 chain_epoch,
789 epoch: certificate.inner().epoch()
790 }
791 );
792 let old_round = self.chain.manager.current_round();
793 self.chain
794 .manager
795 .handle_timeout_certificate(certificate, self.storage.clock().current_time());
796 self.save().await?;
797 let actions = self.create_network_actions(Some(old_round)).await?;
798 Ok((self.chain_info_response().await?, actions))
799 }
800
801 #[instrument(skip_all, fields(
806 chain_id = %self.chain_id(),
807 block_height = %proposal.content.block.height
808 ))]
809 async fn load_proposal_blobs(
810 &mut self,
811 proposal: &BlockProposal,
812 ) -> Result<Vec<Blob>, WorkerError> {
813 let owner = proposal.owner();
814 let BlockProposal {
815 content:
816 ProposalContent {
817 block,
818 round,
819 outcome: _,
820 },
821 original_proposal,
822 signature: _,
823 } = proposal;
824
825 let mut maybe_blobs = self
826 .maybe_get_required_blobs(proposal.required_blob_ids(), None)
827 .await?;
828 let missing_blob_ids = missing_blob_ids(&maybe_blobs);
829 if !missing_blob_ids.is_empty() {
830 let chain = &mut self.chain;
831 if chain.ownership().await?.open_multi_leader_rounds {
832 chain.pending_proposed_blobs.clear();
834 }
835 let validated = matches!(original_proposal, Some(OriginalProposal::Regular { .. }));
836 chain
837 .pending_proposed_blobs
838 .try_load_entry_mut(&owner)
839 .await?
840 .update(*round, validated, maybe_blobs)?;
841 self.save().await?;
842 return Err(WorkerError::BlobsNotFound(missing_blob_ids));
843 }
844 let published_blobs = block
845 .published_blob_ids()
846 .iter()
847 .filter_map(|blob_id| maybe_blobs.remove(blob_id).flatten())
848 .collect::<Vec<_>>();
849 Ok(published_blobs)
850 }
851
852 #[instrument(skip_all, fields(
854 chain_id = %self.chain_id(),
855 block_height = %certificate.block().header.height
856 ))]
857 pub(crate) async fn process_validated_block(
858 &mut self,
859 certificate: ValidatedBlockCertificate,
860 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
861 let block = certificate.block();
862
863 let header = &block.header;
864 let height = header.height;
865 self.initialize_and_save_if_needed().await?;
868 let tip_state = self.chain.tip_state.get();
869 ensure!(
870 header.height == tip_state.next_block_height,
871 ChainError::UnexpectedBlockHeight {
872 expected_block_height: tip_state.next_block_height,
873 found_block_height: header.height,
874 }
875 );
876 let (epoch, committee) = self.chain.current_committee().await?;
877 check_block_epoch(epoch, header.chain_id, header.epoch)?;
878 certificate.check(&committee)?;
879 let already_committed_block = self.chain.tip_state.get().already_validated_block(height)?;
880 let should_skip_validated_block = || {
881 self.chain
882 .manager
883 .check_validated_block(&certificate)
884 .map(|outcome| outcome == manager::Outcome::Skip)
885 };
886 if already_committed_block || should_skip_validated_block()? {
887 return Ok((
889 self.chain_info_response().await?,
890 NetworkActions::default(),
891 BlockOutcome::Skipped,
892 ));
893 }
894
895 self.block_values
896 .insert_hashed(Cow::Borrowed(certificate.inner().inner()));
897 let required_blob_ids = block.required_blob_ids();
898 let maybe_blobs = self
899 .maybe_get_required_blobs(required_blob_ids, Some(block.created_blobs()))
900 .await?;
901 let missing_blob_ids = missing_blob_ids(&maybe_blobs);
902 if !missing_blob_ids.is_empty() {
903 self.chain
904 .pending_validated_blobs
905 .update(certificate.round, true, maybe_blobs)?;
906 self.save().await?;
907 return Err(WorkerError::BlobsNotFound(missing_blob_ids));
908 }
909 let blobs = maybe_blobs
910 .into_iter()
911 .filter_map(|(blob_id, maybe_blob)| Some((blob_id, maybe_blob?)))
912 .collect();
913 let old_round = self.chain.manager.current_round();
914 self.chain.manager.create_final_vote(
915 certificate,
916 self.config.key_pair(),
917 self.storage.clock().current_time(),
918 blobs,
919 )?;
920 self.save().await?;
921 let actions = self.create_network_actions(Some(old_round)).await?;
922 Ok((
923 self.chain_info_response().await?,
924 actions,
925 BlockOutcome::Processed,
926 ))
927 }
928
929 #[instrument(skip_all, fields(
931 chain_id = %certificate.block().header.chain_id,
932 height = %certificate.block().header.height,
933 block_hash = %certificate.hash(),
934 ))]
935 pub(crate) async fn process_confirmed_block(
936 &mut self,
937 certificate: ConfirmedBlockCertificate,
938 mode: ProcessConfirmedBlockMode,
939 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
940 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
941 let block = certificate.block();
942 let block_hash = certificate.hash();
943 let height = block.header.height;
944 let chain_id = block.header.chain_id;
945
946 let in_trust_set = self
954 .chain
955 .pre_checkpoint_block_trust
956 .contains(&block_hash)
957 .await?;
958 if in_trust_set {
959 self.chain.pre_checkpoint_block_trust.remove(&block_hash)?;
960 }
961
962 let tip = self.chain.tip_state.get().clone();
964 if !in_trust_set && tip.next_block_height > height {
965 let actions = self.create_network_actions(None).await?;
966 self.register_delivery_notifier(height, &actions, notify_when_messages_are_delivered)
967 .await;
968 return Ok((
969 self.chain_info_response().await?,
970 actions,
971 BlockOutcome::Skipped,
972 ));
973 }
974
975 let committee = self.committee_for_epoch(block.header.epoch).await?;
977 certificate.check(&committee)?;
978
979 let required_blob_ids = block.required_blob_ids();
983 let blobs_result = self
984 .get_required_blobs(required_blob_ids.iter().copied(), block.created_blobs())
985 .await
986 .map(|blobs| blobs.into_values().collect::<Vec<_>>());
987
988 if let Ok(blobs) = &blobs_result {
989 self.storage
990 .write_blobs_and_certificate(blobs, &certificate)
991 .await?;
992 let events = block
993 .body
994 .events
995 .iter()
996 .flatten()
997 .map(|event| (event.id(chain_id), event.value.clone()));
998 self.storage.write_events(events).await?;
999 }
1000
1001 let blob_state = certificate.value().to_blob_state(blobs_result.is_ok());
1003 let blob_ids = required_blob_ids.into_iter().collect::<Vec<_>>();
1004 self.storage
1005 .maybe_write_blob_states(&blob_ids, blob_state)
1006 .await?;
1007
1008 let blobs = blobs_result?
1009 .into_iter()
1010 .map(|blob| (blob.id(), blob))
1011 .collect::<BTreeMap<_, _>>();
1012
1013 use ProcessConfirmedBlockMode::{Auto, Execute, Preprocess};
1021 let gap = tip.next_block_height != height;
1022 let starts_with_checkpoint = block.starts_with_checkpoint();
1023 match (mode, gap, starts_with_checkpoint) {
1024 (Preprocess, _, _) | (Auto, true, false) => {
1025 self.preprocess_certified_block(certificate, notify_when_messages_are_delivered)
1026 .await
1027 }
1028 (Execute, true, false) => Err(WorkerError::InvalidBlockChaining),
1029 (Auto | Execute, true, true) => {
1030 self.execute_block_with_checkpoint_restore(
1031 certificate,
1032 blobs,
1033 notify_when_messages_are_delivered,
1034 )
1035 .await
1036 }
1037 (Auto | Execute, false, _) => {
1038 self.execute_contiguous_block(
1039 certificate,
1040 blobs,
1041 tip,
1042 notify_when_messages_are_delivered,
1043 )
1044 .await
1045 }
1046 }
1047 }
1048
1049 async fn preprocess_certified_block(
1052 &mut self,
1053 certificate: ConfirmedBlockCertificate,
1054 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1055 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
1056 let block_hash = certificate.hash();
1057 let block = certificate.block();
1058 let chain_id = block.header.chain_id;
1059 let height = block.header.height;
1060
1061 let tracked = self.reconcile_tracked_outboxes().await?;
1062 let updated_event_streams = self
1063 .chain
1064 .preprocess_block(certificate.value(), tracked.as_deref().map(|h| h.inner()))
1065 .await?;
1066 self.save().await?;
1067 let mut actions = self.create_network_actions(None).await?;
1068 if !updated_event_streams.is_empty() {
1069 actions.notifications.push(Notification {
1070 chain_id,
1071 reason: Reason::NewEvents {
1072 height,
1073 block_hash,
1074 event_streams: updated_event_streams,
1075 },
1076 });
1077 }
1078 trace!("Preprocessed confirmed block {height}");
1079 self.register_delivery_notifier(height, &actions, notify_when_messages_are_delivered)
1080 .await;
1081 Ok((
1082 self.chain_info_response().await?,
1083 actions,
1084 BlockOutcome::Preprocessed,
1085 ))
1086 }
1087
1088 async fn execute_block_with_checkpoint_restore(
1093 &mut self,
1094 certificate: ConfirmedBlockCertificate,
1095 blobs: BTreeMap<BlobId, Blob>,
1096 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1097 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
1098 let (bytes, chain_id, height, previous_block_hash, outbox_block_hashes, inbox_cursors) = {
1099 let block = certificate.block();
1100 let Some(OracleResponse::Checkpoint {
1101 execution_state_blobs,
1102 outbox_block_hashes,
1103 inbox_cursors,
1104 ..
1105 }) = block.body.oracle_responses.first().and_then(|r| r.first())
1106 else {
1107 return Err(ChainError::InternalError(
1108 "Checkpoint block missing OracleResponse::Checkpoint".into(),
1109 )
1110 .into());
1111 };
1112 let mut bytes = Vec::new();
1113 let mut missing = Vec::new();
1114 for hash in execution_state_blobs {
1115 let blob_id = BlobId::new(*hash, BlobType::CheckpointExecutionState);
1116 match blobs.get(&blob_id) {
1117 Some(blob) => bytes.extend_from_slice(blob.bytes()),
1118 None => missing.push(blob_id),
1119 }
1120 }
1121 ensure!(missing.is_empty(), WorkerError::BlobsNotFound(missing));
1122 (
1123 bytes,
1124 block.header.chain_id,
1125 block.header.height,
1126 block.header.previous_block_hash,
1127 outbox_block_hashes.clone(),
1128 inbox_cursors.clone(),
1129 )
1130 };
1131 let mut missing_blocks = Vec::new();
1139 for hash in &outbox_block_hashes {
1140 if !self.storage.contains_certificate(*hash).await? {
1141 missing_blocks.push(*hash);
1142 }
1143 }
1144 if !missing_blocks.is_empty() {
1145 for hash in &missing_blocks {
1146 self.chain.pre_checkpoint_block_trust.insert(hash)?;
1147 }
1148 self.save().await?;
1149 return Err(WorkerError::BlocksNotFound(missing_blocks));
1150 }
1151 self.chain
1152 .execution_state
1153 .restore_from_content(&bytes)
1154 .await?;
1155 self.chain = self.storage.load_chain(chain_id).await?;
1158 let heights = self.chain.collect_unfinalized_heights().await?;
1166 ensure!(
1167 heights.len() == outbox_block_hashes.len(),
1168 ChainError::InternalError(format!(
1169 "checkpoint oracle response has {} outbox block hashes but the \
1170 restored state references {} distinct heights",
1171 outbox_block_hashes.len(),
1172 heights.len(),
1173 ))
1174 );
1175 for (height, hash) in heights.into_iter().zip(outbox_block_hashes) {
1176 self.chain.block_hashes.insert(&height, hash)?;
1177 }
1178 let tracked = self.tracked_full_chains();
1185 self.chain
1186 .restore_outboxes_from_unfinalized(tracked.as_deref())
1187 .await?;
1188 for (origin, cursor) in inbox_cursors {
1189 let mut inbox = self.chain.inboxes.try_load_entry_mut(&origin).await?;
1190 inbox.restore_from_checkpoint(cursor).await?;
1191 }
1192 for (stream_id, count) in self
1199 .chain
1200 .execution_state
1201 .system
1202 .stream_event_counts
1203 .index_values()
1204 .await?
1205 {
1206 self.chain.next_expected_events.insert(
1209 &stream_id,
1210 StreamCounts {
1211 first_index: count,
1212 next_index: count,
1213 },
1214 )?;
1215 }
1216 let new_tip = ChainTipState {
1225 block_hash: previous_block_hash,
1226 next_block_height: height,
1227 };
1228 self.chain.tip_state.set(new_tip.clone());
1229 self.chain
1232 .chain_initialized_at
1233 .set(self.storage.clock().current_time());
1234 self.save().await?;
1235 self.execute_contiguous_block(
1236 certificate,
1237 blobs,
1238 new_tip,
1239 notify_when_messages_are_delivered,
1240 )
1241 .await
1242 }
1243
1244 async fn execute_contiguous_block(
1247 &mut self,
1248 certificate: ConfirmedBlockCertificate,
1249 mut blobs: BTreeMap<BlobId, Blob>,
1250 tip: ChainTipState,
1251 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1252 ) -> Result<(ChainInfoResponse, NetworkActions, BlockOutcome), WorkerError> {
1253 let block_hash = certificate.hash();
1254 let block = certificate.block();
1255 let chain_id = block.header.chain_id;
1256 let height = block.header.height;
1257
1258 ensure!(
1260 tip.block_hash == block.header.previous_block_hash,
1261 WorkerError::InvalidBlockChaining
1262 );
1263
1264 self.initialize_and_save_if_needed().await?;
1267 let (epoch, _) = self.chain.current_committee().await?;
1268 check_block_epoch(epoch, chain_id, block.header.epoch)?;
1269
1270 if certificate.first_round() {
1276 ensure!(
1277 certificate.round() == self.chain.ownership().await?.first_round(),
1278 ChainError::FalseFirstRoundAttestation
1279 );
1280 }
1281
1282 let published_blobs = block
1283 .published_blob_ids()
1284 .iter()
1285 .filter_map(|blob_id| blobs.remove(blob_id))
1286 .collect::<Vec<_>>();
1287
1288 let local_time = self.storage.clock().current_time();
1289 if block.header.timestamp.duration_since(local_time) > self.config.block_time_grace_period {
1290 warn!(
1291 block_timestamp = %block.header.timestamp,
1292 %local_time,
1293 "Confirmed block has a timestamp in the future beyond the block time grace period"
1294 );
1295 }
1296 let tracked = self.reconcile_tracked_outboxes().await?;
1297 let chain = &mut self.chain;
1298 chain
1299 .remove_bundles_from_inboxes(
1300 block.header.timestamp,
1301 false,
1302 block.body.incoming_bundles(),
1303 )
1304 .await?;
1305 let confirmed_block = if let Some(mut execution_state) = self
1306 .execution_state_cache
1307 .as_ref()
1308 .and_then(|cache| cache.remove(&block_hash))
1309 {
1310 chain.execution_state = execution_state
1311 .with_context(|ctx| {
1312 chain
1313 .execution_state
1314 .context()
1315 .clone_with_base_key(ctx.base_key().bytes.clone())
1316 })
1317 .await;
1318 certificate.into_value()
1319 } else {
1320 let (proposed_block, outcome) = certificate.into_value().into_block().into_proposal();
1321 let oracle_responses = Some(outcome.oracle_responses.clone());
1322 let (proposed_block, verified, _resource_tracker, _) = chain
1323 .execute_block(
1324 proposed_block,
1325 local_time,
1326 None,
1327 &published_blobs,
1328 oracle_responses,
1329 BundleExecutionPolicy::committed(),
1330 BlockExecutionPhase::HandleConfirmed,
1331 )
1332 .await?;
1333 if outcome != verified {
1335 return Err(ChainError::CorruptedChainState(format!(
1336 "computed block outcome differs from the certificate.\n\
1337 Computed: {verified:#?}\n\
1338 Submitted: {outcome:#?}"
1339 ))
1340 .into());
1341 }
1342 ConfirmedBlock::new(Block::new(proposed_block, verified))
1343 };
1344
1345 let updated_streams = chain
1346 .apply_confirmed_block(
1347 &confirmed_block,
1348 local_time,
1349 tracked.as_deref().map(|h| h.inner()),
1350 )
1351 .await?;
1352 let mut actions = self.create_network_actions(None).await?;
1353 trace!("Processed confirmed block {height}");
1354 actions.notifications.push(Notification {
1355 chain_id,
1356 reason: Reason::NewBlock {
1357 height,
1358 hash: block_hash,
1359 },
1360 });
1361 if !updated_streams.is_empty() {
1362 actions.notifications.push(Notification {
1363 chain_id,
1364 reason: Reason::NewEvents {
1365 height,
1366 block_hash,
1367 event_streams: updated_streams,
1368 },
1369 });
1370 }
1371 self.save().await?;
1372
1373 self.block_values
1374 .insert_hashed(Cow::Owned(confirmed_block.into_inner()));
1375
1376 self.register_delivery_notifier(height, &actions, notify_when_messages_are_delivered)
1377 .await;
1378
1379 Ok((
1380 self.chain_info_response().await?,
1381 actions,
1382 BlockOutcome::Processed,
1383 ))
1384 }
1385
1386 #[instrument(level = "trace", skip(self, notify_when_messages_are_delivered))]
1389 async fn register_delivery_notifier(
1390 &self,
1391 height: BlockHeight,
1392 actions: &NetworkActions,
1393 notify_when_messages_are_delivered: Option<oneshot::Sender<()>>,
1394 ) {
1395 if let Some(notifier) = notify_when_messages_are_delivered {
1396 if actions
1397 .cross_chain_requests
1398 .iter()
1399 .any(|request| request.has_messages_lower_or_equal_than(height))
1400 {
1401 self.delivery_notifier.register(height, notifier);
1402 } else {
1403 if let Err(()) = notifier.send(()) {
1406 debug!("Failed to notify message delivery to caller (early case)");
1407 }
1408 }
1409 }
1410 }
1411
1412 #[instrument(level = "debug", skip(self, bundles), fields(chain_id = %self.chain_id()))]
1414 pub(crate) async fn process_cross_chain_update(
1415 &mut self,
1416 origin: ChainId,
1417 bundles: Vec<(Epoch, MessageBundle)>,
1418 sender_previous_height: Option<BlockHeight>,
1419 ) -> Result<CrossChainUpdateResult, WorkerError> {
1420 let mut inbox = self.chain.inboxes.try_load_entry_mut(&origin).await?;
1422 let next_height_to_receive = inbox.next_block_height_to_receive()?;
1423 let last_anticipated_block_height = inbox
1424 .removed_bundles
1425 .back()
1426 .await?
1427 .map(|bundle| bundle.height);
1428
1429 if let Some(prev) = sender_previous_height {
1432 if prev >= next_height_to_receive {
1433 let chain_id = self.chain_id();
1434 if self.config.allow_revert_confirm && self.config.recovery_allowed_for(&chain_id) {
1435 warn!(
1436 %chain_id,
1437 "Inbox gap detected from {origin}: \
1438 sender declares previous height {prev} but we only have up to \
1439 {next_height_to_receive}; requesting resend",
1440 );
1441 return Ok(CrossChainUpdateResult::GapDetected {
1442 origin,
1443 retransmit_from: next_height_to_receive,
1444 });
1445 }
1446 return Err(ChainError::InboxGapDetected {
1447 chain_id,
1448 origin,
1449 expected_height: prev,
1450 actual_height: bundles.first().map(|(_, b)| b.height).unwrap_or_default(),
1451 }
1452 .into());
1453 }
1454 }
1455
1456 let bundles = self
1457 .select_message_bundles(
1458 &origin,
1459 next_height_to_receive,
1460 last_anticipated_block_height,
1461 bundles,
1462 )
1463 .await?;
1464 let Some(last_updated_height) = bundles.last().map(|bundle| bundle.height) else {
1465 return Ok(CrossChainUpdateResult::NothingToDo);
1466 };
1467 let local_time = self.storage.clock().current_time();
1469 let mut previous_height = None;
1470 for bundle in bundles {
1471 let add_to_received_log = previous_height != Some(bundle.height);
1472 previous_height = Some(bundle.height);
1473 self.chain
1475 .receive_message_bundle_with_inbox(
1476 &mut inbox,
1477 &origin,
1478 bundle,
1479 local_time,
1480 add_to_received_log,
1481 )
1482 .await?;
1483 }
1484 inbox.observe_size_metric();
1485 drop(inbox);
1486 if !self.config.allow_inactive_chains && !self.chain.is_active().await? {
1487 warn!(
1491 chain_id = %self.chain_id(),
1492 "Refusing to deliver messages from {origin} \
1493 at height {last_updated_height} because the recipient is still inactive",
1494 );
1495 return Ok(CrossChainUpdateResult::NothingToDo);
1496 }
1497 Ok(CrossChainUpdateResult::Updated(last_updated_height))
1498 }
1499
1500 #[instrument(skip_all, fields(
1502 chain_id = %self.chain_id(),
1503 %recipient,
1504 %latest_height
1505 ))]
1506 pub(crate) async fn confirm_updated_recipient(
1507 &mut self,
1508 recipient: ChainId,
1509 latest_height: BlockHeight,
1510 ) -> Result<bool, WorkerError> {
1511 let tracked = self.reconcile_tracked_outboxes().await?;
1514 Ok(self
1517 .chain
1518 .mark_messages_as_received(
1519 &recipient,
1520 latest_height,
1521 tracked.as_deref().map(|h| h.inner()),
1522 )
1523 .await?
1524 && self.chain.all_messages_delivered_up_to(latest_height))
1525 }
1526
1527 pub(crate) fn notify_delivery(&self, height: BlockHeight) {
1529 self.delivery_notifier.notify(height);
1530 }
1531
1532 pub(crate) async fn process_batch(
1537 &mut self,
1538 requests: Vec<BatchRequest>,
1539 ) -> Result<(), WorkerError> {
1540 let mut update_results = Vec::new();
1541 let mut confirm_results = Vec::new();
1542 let mut need_save = false;
1543 let mut need_rollback = false;
1544 let mut recovery_error = None;
1545 let mut max_delivered_height: Option<BlockHeight> = None;
1546
1547 for request in requests {
1548 match request {
1549 BatchRequest::Update {
1550 origin,
1551 bundles,
1552 previous_height,
1553 result_sender,
1554 } => {
1555 if need_rollback {
1556 send_result(result_sender, Err(WorkerError::BatchRolledBack));
1557 continue;
1558 }
1559 let result = self
1560 .process_cross_chain_update(origin, bundles, previous_height)
1561 .await;
1562 let update_result = match result {
1563 Ok(update_result) => update_result,
1564 Err(error) => {
1565 need_rollback = true;
1566 let (recovery, to_send) = classify_processing_error(error);
1567 recovery_error = recovery_error.or(recovery);
1568 send_result(result_sender, Err(to_send));
1569 continue;
1570 }
1571 };
1572 match &update_result {
1573 CrossChainUpdateResult::Updated(_) => need_save = true,
1574 CrossChainUpdateResult::GapDetected { .. }
1575 | CrossChainUpdateResult::NothingToDo => {}
1576 }
1577 update_results.push((result_sender, update_result));
1578 }
1579 BatchRequest::Confirm {
1580 recipient,
1581 latest_height,
1582 result_sender,
1583 } => {
1584 if need_rollback {
1585 send_result(result_sender, Err(WorkerError::BatchRolledBack));
1586 continue;
1587 }
1588 match self
1589 .confirm_updated_recipient(recipient, latest_height)
1590 .await
1591 {
1592 Ok(fully_delivered) => {
1593 need_save = true;
1594 if fully_delivered {
1595 max_delivered_height = Some(
1596 max_delivered_height
1597 .map_or(latest_height, |h| h.max(latest_height)),
1598 );
1599 }
1600 confirm_results.push((result_sender, recipient));
1601 }
1602 Err(error) => {
1603 need_rollback = true;
1604 let (recovery, to_send) = classify_processing_error(error);
1605 recovery_error = recovery_error.or(recovery);
1606 send_result(result_sender, Err(to_send));
1607 }
1608 }
1609 }
1610 }
1611 }
1612 let mut save_error = None;
1613 if !need_rollback && need_save {
1614 if let Err(error) = self.save().await {
1615 tracing::error!(%error, "failed to save batch; rolling back");
1616 need_rollback = true;
1617 save_error = Some(error);
1618 }
1619 }
1620 if need_rollback {
1621 for (result_sender, _) in update_results {
1622 send_result(result_sender, Err(WorkerError::BatchRolledBack));
1623 }
1624 for (result_sender, _) in confirm_results {
1625 send_result(result_sender, Err(WorkerError::BatchRolledBack));
1626 }
1627 return match save_error.or(recovery_error) {
1637 Some(error) => Err(error),
1638 None => Ok(()),
1639 };
1640 }
1641
1642 if let Some(height) = max_delivered_height {
1643 self.notify_delivery(height);
1644 }
1645
1646 for (result_sender, update_result) in update_results {
1647 send_result(result_sender, Ok(update_result));
1648 }
1649 for (result_sender, recipient) in confirm_results {
1650 let result = self
1651 .create_cross_chain_actions_for_recipient(recipient)
1652 .await;
1653 send_result(result_sender, result);
1654 }
1655 Ok(())
1656 }
1657
1658 #[instrument(skip_all, fields(
1663 chain_id = %self.chain_id(),
1664 %recipient,
1665 %retransmit_from,
1666 ))]
1667 pub(crate) async fn handle_revert_confirm(
1668 &mut self,
1669 recipient: ChainId,
1670 retransmit_from: BlockHeight,
1671 ) -> Result<NetworkActions, WorkerError> {
1672 self.reconcile_tracked_outboxes().await?;
1673 let Some(latest_height) = self
1676 .chain
1677 .execution_state
1678 .previous_message_blocks
1679 .get(&recipient)
1680 .await?
1681 else {
1682 warn!("RevertConfirm: no record of sending to {recipient}");
1683 return Ok(NetworkActions::default());
1684 };
1685
1686 let mut heights_to_re_add = Vec::new();
1687 let mut current_height = latest_height;
1688 while current_height >= retransmit_from {
1689 heights_to_re_add.push(current_height);
1693 let hash = match &*self
1695 .chain
1696 .block_hashes_for_heights([current_height])
1697 .await?
1698 {
1699 [hash] => *hash,
1700 _ => {
1701 return Err(WorkerError::BlockHashNotFound {
1702 height: current_height,
1703 chain_id: self.chain_id(),
1704 })
1705 }
1706 };
1707 let block = self
1708 .read_confirmed_blocks(&[hash])
1709 .await?
1710 .pop()
1711 .flatten()
1712 .ok_or_else(|| WorkerError::LocalBlockNotFound {
1713 height: current_height,
1714 chain_id: self.chain_id(),
1715 })?;
1716 match block.block().body.previous_message_blocks.get(&recipient) {
1717 Some((_, prev_height)) if *prev_height >= retransmit_from => {
1718 current_height = *prev_height;
1719 }
1720 _ => break,
1721 }
1722 }
1723
1724 let new_heights = self
1726 .chain
1727 .outboxes
1728 .try_load_entry_mut(&recipient)
1729 .await?
1730 .revert(&heights_to_re_add)
1731 .await?;
1732
1733 if new_heights.is_empty() {
1734 debug!("RevertConfirm: all heights already in outbox for {recipient}");
1735 return Ok(NetworkActions::default());
1736 }
1737
1738 let new_heights_len = new_heights.len();
1741 if self.is_tracked(&recipient) {
1742 for h in new_heights {
1743 *self.chain.outbox_counters.get_mut().entry(h).or_default() += 1;
1744 }
1745 self.chain.nonempty_outboxes.get_mut().insert(recipient);
1746 }
1747
1748 let actions = self
1750 .create_cross_chain_actions_for_recipient(recipient)
1751 .await?;
1752
1753 self.save().await?;
1755
1756 warn!(
1757 "RevertConfirm: re-added {new_heights_len} heights to outbox for {recipient}, \
1758 starting from height {retransmit_from}"
1759 );
1760
1761 Ok(actions)
1762 }
1763
1764 pub(crate) async fn maybe_reset_corrupted_chain_state(
1768 &mut self,
1769 ) -> Result<Option<Vec<CrossChainRequest>>, WorkerError> {
1770 let Some(min_duration) = self.config.reset_on_corrupted_chain_state else {
1771 return Ok(None);
1772 };
1773 let chain_id = self.chain_id();
1774 if !self.config.recovery_allowed_for(&chain_id) {
1775 return Ok(None);
1776 }
1777 let local_time = self.storage.clock().current_time();
1778 let initialized_time = *self.chain.chain_initialized_at.get();
1779 let elapsed = local_time.duration_since(initialized_time);
1780 if elapsed < min_duration {
1781 warn!(
1782 %chain_id, ?elapsed, ?min_duration,
1783 "Not resetting corrupted chain state; not enough time elapsed \
1784 since the chain was last initialized"
1785 );
1786 return Ok(None);
1787 }
1788 warn!(%chain_id, "Corrupted chain state detected; resetting and re-executing");
1789 Ok(Some(self.reset_and_reexecute_chain().await?))
1790 }
1791
1792 #[instrument(skip_all, fields(
1796 chain_id = %self.chain_id(),
1797 ))]
1798 pub(crate) async fn reset_and_reexecute_chain(
1799 &mut self,
1800 ) -> Result<Vec<CrossChainRequest>, WorkerError> {
1801 let chain_id = self.chain_id();
1802 let tip_height = self.chain.tip_state.get().next_block_height;
1803
1804 let sender_ids = self.chain.inboxes.indices().await?;
1806 let block_hashes = self.chain.block_hashes.index_values().await?;
1807 let restore_from =
1812 (*self.chain.latest_checkpoint_height.get()).unwrap_or(BlockHeight::ZERO);
1813
1814 let manager_snapshot = ManagerSafetySnapshot::capture(&self.chain.manager).await?;
1817
1818 self.wipe_and_reload_chain().await?;
1827 self.knows_chain_is_active = false;
1828 warn!(
1829 %chain_id,
1830 "Cleared chain state up to height {tip_height}; \
1831 re-executing blocks from height {restore_from}"
1832 );
1833
1834 let total = block_hashes
1839 .iter()
1840 .filter(|(height, _)| *height >= restore_from)
1841 .count();
1842 let mut replayed = 0;
1843 for (height, hash) in block_hashes {
1844 if height < restore_from {
1845 continue;
1846 }
1847 if replayed % 1000 == 0 {
1848 info!(
1849 %chain_id, replayed, total,
1850 "Re-executing confirmed blocks after reset"
1851 );
1852 }
1853 replayed += 1;
1854 let cert = self
1855 .storage
1856 .read_certificate(hash)
1857 .await?
1858 .map(CacheArc::unwrap_or_clone)
1859 .ok_or_else(|| WorkerError::LocalBlockNotFound { height, chain_id })?;
1860 Box::pin(self.process_confirmed_block(cert, ProcessConfirmedBlockMode::Execute, None))
1861 .await?;
1862 }
1863
1864 let new_tip_height = self.chain.tip_state.get().next_block_height;
1872 if new_tip_height == tip_height {
1873 manager_snapshot.restore(&mut self.chain.manager)?;
1874 self.save().await?;
1875 } else {
1876 warn!(
1877 %tip_height, %new_tip_height,
1878 "Dropping manager snapshot: pre-reset tip differs from post-reset tip"
1879 );
1880 }
1881
1882 let revert_requests = sender_ids
1885 .into_iter()
1886 .map(|sender| CrossChainRequest::RevertConfirm {
1887 sender,
1888 recipient: chain_id,
1889 retransmit_from: BlockHeight::ZERO,
1890 })
1891 .collect::<Vec<_>>();
1892
1893 warn!(
1894 tip_height = %self.chain.tip_state.get().next_block_height,
1895 num_revert_confirms = revert_requests.len(),
1896 "Chain reset and re-executed; sending RevertConfirm to senders"
1897 );
1898
1899 Ok(revert_requests)
1900 }
1901
1902 #[instrument(skip_all, fields(
1903 chain_id = %self.chain_id(),
1904 num_trackers = %new_trackers.len()
1905 ))]
1906 pub(crate) async fn update_received_certificate_trackers(
1907 &mut self,
1908 new_trackers: BTreeMap<ValidatorPublicKey, u64>,
1909 ) -> Result<(), WorkerError> {
1910 self.chain
1911 .update_received_certificate_trackers(new_trackers);
1912 self.save().await?;
1913 Ok(())
1914 }
1915
1916 #[instrument(skip_all, fields(
1918 chain_id = %self.chain_id(),
1919 start = %start,
1920 end = %end
1921 ))]
1922 pub(crate) async fn get_preprocessed_block_hashes(
1923 &self,
1924 start: BlockHeight,
1925 end: BlockHeight,
1926 ) -> Result<Vec<CryptoHash>, WorkerError> {
1927 let mut hashes = Vec::new();
1928 let mut height = start;
1929 while height < end {
1930 match self.chain.block_hashes.get(&height).await? {
1931 Some(hash) => hashes.push(hash),
1932 None => break,
1933 }
1934 height = height.try_add_one()?;
1935 }
1936 Ok(hashes)
1937 }
1938
1939 #[instrument(skip_all, fields(
1941 chain_id = %self.chain_id(),
1942 origin = %origin
1943 ))]
1944 pub(crate) async fn get_inbox_next_height(
1945 &self,
1946 origin: ChainId,
1947 ) -> Result<BlockHeight, WorkerError> {
1948 Ok(match self.chain.inboxes.try_load_entry(&origin).await? {
1949 Some(inbox) => inbox.next_block_height_to_receive()?,
1950 None => BlockHeight::ZERO,
1951 })
1952 }
1953
1954 #[instrument(skip_all, fields(
1957 chain_id = %self.chain_id(),
1958 num_blob_ids = %blob_ids.len()
1959 ))]
1960 pub(crate) async fn get_locking_blobs(
1961 &self,
1962 blob_ids: Vec<BlobId>,
1963 ) -> Result<Option<Vec<Blob>>, WorkerError> {
1964 let results = self
1965 .chain
1966 .manager
1967 .locking_blobs
1968 .multi_get(&blob_ids)
1969 .await?;
1970 Ok(results.into_iter().collect())
1971 }
1972
1973 pub(crate) async fn get_block_hashes(
1975 &self,
1976 heights: Vec<BlockHeight>,
1977 ) -> Result<Vec<CryptoHash>, WorkerError> {
1978 Ok(self.chain.block_hashes_for_heights(heights).await?)
1979 }
1980
1981 pub(crate) async fn get_proposed_blobs(
1983 &self,
1984 blob_ids: Vec<BlobId>,
1985 ) -> Result<Vec<Blob>, WorkerError> {
1986 let results = self
1987 .chain
1988 .manager
1989 .proposed_blobs
1990 .multi_get(&blob_ids)
1991 .await?;
1992 let mut blobs = Vec::with_capacity(blob_ids.len());
1993 let mut missing = Vec::new();
1994 for (blob_id, maybe_blob) in blob_ids.into_iter().zip(results) {
1995 match maybe_blob {
1996 Some(blob) => blobs.push(blob),
1997 None => missing.push(blob_id),
1998 }
1999 }
2000 if !missing.is_empty() {
2001 return Err(WorkerError::BlobsNotFound(missing));
2002 }
2003 Ok(blobs)
2004 }
2005
2006 pub(crate) async fn get_event_subscriptions(
2008 &self,
2009 ) -> Result<EventSubscriptionsResult, WorkerError> {
2010 Ok(self
2011 .chain
2012 .execution_state
2013 .system
2014 .event_subscriptions
2015 .index_values()
2016 .await?)
2017 }
2018
2019 pub(crate) async fn get_stream_indices(
2025 &self,
2026 stream_id: StreamId,
2027 ) -> Result<StreamCounts, WorkerError> {
2028 Ok(self
2029 .chain
2030 .next_expected_events
2031 .get(&stream_id)
2032 .await?
2033 .unwrap_or_default())
2034 }
2035
2036 pub(crate) async fn get_next_expected_events(
2038 &self,
2039 stream_ids: Vec<StreamId>,
2040 ) -> Result<BTreeMap<StreamId, u32>, WorkerError> {
2041 let values = self
2042 .chain
2043 .next_expected_events
2044 .multi_get(&stream_ids)
2045 .await?;
2046 Ok(stream_ids
2047 .into_iter()
2048 .zip(values)
2049 .filter_map(|(id, val)| Some((id, val?.next_index)))
2050 .collect())
2051 }
2052
2053 pub(crate) async fn get_received_certificate_trackers(
2055 &self,
2056 ) -> Result<HashMap<ValidatorPublicKey, u64>, WorkerError> {
2057 Ok(self.chain.received_certificate_trackers.get().clone())
2058 }
2059
2060 pub(crate) async fn get_tip_state_and_outbox_info(
2062 &self,
2063 receiver_id: ChainId,
2064 ) -> Result<(BlockHeight, Option<BlockHeight>), WorkerError> {
2065 let next_block_height = self.chain.tip_state.get().next_block_height;
2066 let next_height_to_schedule = self
2067 .chain
2068 .outboxes
2069 .try_load_entry(&receiver_id)
2070 .await?
2071 .map(|outbox| *outbox.next_height_to_schedule.get());
2072 Ok((next_block_height, next_height_to_schedule))
2073 }
2074
2075 pub(crate) fn get_next_height_to_preprocess(&self) -> BlockHeight {
2077 *self.chain.next_height_to_preprocess.get()
2078 }
2079
2080 #[instrument(skip_all, fields(
2082 chain_id = %self.chain_id(),
2083 height = %height,
2084 round = %round
2085 ))]
2086 async fn vote_for_leader_timeout(
2087 &mut self,
2088 height: BlockHeight,
2089 round: Round,
2090 ) -> Result<(), WorkerError> {
2091 let chain = &mut self.chain;
2092 ensure!(
2093 height == chain.tip_state.get().next_block_height,
2094 WorkerError::UnexpectedBlockHeight {
2095 expected_block_height: chain.tip_state.get().next_block_height,
2096 found_block_height: height
2097 }
2098 );
2099 let epoch = chain.execution_state.system.epoch.get();
2100 let chain_id = chain.chain_id();
2101 let key_pair = self.config.key_pair();
2102 let local_time = self.storage.clock().current_time();
2103 if chain
2104 .manager
2105 .create_timeout_vote(chain_id, height, round, *epoch, key_pair, local_time)?
2106 {
2107 self.save().await?;
2108 }
2109 Ok(())
2110 }
2111
2112 #[instrument(skip_all, fields(
2117 chain_id = %self.chain_id()
2118 ))]
2119 async fn vote_for_fallback(&mut self) -> Result<(), WorkerError> {
2120 let chain = &mut self.chain;
2121 let epoch = *chain.execution_state.system.epoch.get();
2122 let Some(admin_chain_id) = chain.execution_state.system.admin_chain_id.get() else {
2123 return Ok(());
2124 };
2125
2126 let next_epoch_index = epoch.0.saturating_add(1);
2128 let event_id = EventId {
2129 chain_id: *admin_chain_id,
2130 stream_id: StreamId::system(EPOCH_STREAM_NAME),
2131 index: next_epoch_index,
2132 };
2133
2134 let Some(event_bytes) = self.storage.read_event(event_id).await? else {
2135 return Ok(()); };
2137
2138 let event_data: EpochEventData = bcs::from_bytes(&event_bytes)?;
2139 let elapsed = self
2140 .storage
2141 .clock()
2142 .current_time()
2143 .delta_since(event_data.timestamp);
2144 if elapsed >= chain.ownership().await?.timeout_config.fallback_duration {
2145 let chain_id = chain.chain_id();
2146 let height = chain.tip_state.get().next_block_height;
2147 let key_pair = self.config.key_pair();
2148 if chain
2149 .manager
2150 .vote_fallback(chain_id, height, epoch, key_pair)
2151 {
2152 self.save().await?;
2153 }
2154 }
2155 Ok(())
2156 }
2157
2158 #[instrument(skip_all, fields(
2159 chain_id = %self.chain_id(),
2160 blob_id = %blob.id()
2161 ))]
2162 pub(crate) async fn handle_pending_blob(
2163 &mut self,
2164 blob: Blob,
2165 ) -> Result<ChainInfoResponse, WorkerError> {
2166 let mut was_expected = self
2167 .chain
2168 .pending_validated_blobs
2169 .maybe_insert(&blob)
2170 .await?;
2171 for (_, mut pending_blobs) in self
2172 .chain
2173 .pending_proposed_blobs
2174 .try_load_all_entries_mut()
2175 .await?
2176 {
2177 if !pending_blobs.validated.get() {
2178 let (_, committee) = self.chain.current_committee().await?;
2179 let policy = committee.policy();
2180 policy
2181 .check_blob_size(blob.content())
2182 .with_execution_context(ChainExecutionContext::Block)?;
2183 ensure!(
2184 u64::try_from(pending_blobs.pending_blobs.iterative_count().await?)
2185 .is_ok_and(|count| count < policy.maximum_published_blobs),
2186 WorkerError::TooManyPublishedBlobs(policy.maximum_published_blobs)
2187 );
2188 }
2189 was_expected = was_expected || pending_blobs.maybe_insert(&blob).await?;
2190 }
2191 ensure!(was_expected, WorkerError::UnexpectedBlob);
2192 self.save().await?;
2193 self.chain_info_response().await
2194 }
2195
2196 #[cfg(with_testing)]
2201 #[instrument(skip_all, fields(
2202 chain_id = %self.chain_id(),
2203 height = %height
2204 ))]
2205 pub(crate) async fn read_certificate(
2206 &self,
2207 height: BlockHeight,
2208 ) -> Result<Option<CacheArc<ConfirmedBlockCertificate>>, WorkerError> {
2209 let certificate_hash = match self.chain.block_hashes.get(&height).await? {
2210 Some(hash) => hash,
2211 None => return Ok(None),
2212 };
2213 let certificate = self
2214 .storage
2215 .read_certificate(certificate_hash)
2216 .await?
2217 .ok_or(WorkerError::BlocksNotFound(vec![certificate_hash]))?;
2218 Ok(Some(certificate))
2219 }
2220
2221 #[instrument(skip_all, fields(
2223 chain_id = %self.chain_id(),
2224 query_application_id = %query.application_id()
2225 ))]
2226 pub(crate) async fn query_application(
2227 &mut self,
2228 query: Query,
2229 block_hash: Option<CryptoHash>,
2230 ) -> Result<(QueryOutcome, BlockHeight), WorkerError> {
2231 self.initialize_and_save_if_needed().await?;
2232 let next_block_height = self.chain.tip_state.get().next_block_height;
2233 let local_time = self.storage.clock().current_time();
2234 let cached_state = block_hash
2237 .zip(self.execution_state_cache.as_ref())
2238 .and_then(|(h, cache)| Some(h).zip(cache.remove(&h)));
2239 if let Some((requested_block, mut state)) = cached_state {
2240 let next_block_height = next_block_height
2241 .try_add_one()
2242 .expect("block height to not overflow");
2243 let context = QueryContext {
2244 chain_id: self.chain_id(),
2245 next_block_height,
2246 local_time,
2247 };
2248 let outcome = state
2249 .with_context(|ctx| {
2250 self.chain
2251 .execution_state
2252 .context()
2253 .clone_with_base_key(ctx.base_key().bytes.clone())
2254 })
2255 .await
2256 .query_application(context, query, self.service_runtime_endpoint.as_mut())
2257 .await
2258 .with_execution_context(ChainExecutionContext::Query)?;
2259 if let Some(cache) = &self.execution_state_cache {
2260 cache.insert(&requested_block, state);
2261 }
2262 Ok((outcome, next_block_height))
2263 } else {
2264 if block_hash.is_some() {
2265 tracing::debug!(
2266 "requested block hash not found in cache, querying committed state"
2267 );
2268 }
2269 let outcome = self
2270 .chain
2271 .query_application(local_time, query, self.service_runtime_endpoint.as_mut())
2272 .await?;
2273 Ok((outcome, next_block_height))
2274 }
2275 }
2276
2277 #[instrument(skip_all, fields(
2283 chain_id = %self.chain_id(),
2284 application_id = %application_id
2285 ))]
2286 pub(crate) async fn describe_application_readonly(
2287 &self,
2288 application_id: ApplicationId,
2289 ) -> Result<ApplicationDescription, WorkerError> {
2290 let blob_id = application_id.description_blob_id();
2291 let blob = self
2292 .storage
2293 .read_blob(blob_id)
2294 .await?
2295 .ok_or(WorkerError::BlobsNotFound(vec![blob_id]))?;
2296 Ok(bcs::from_bytes(blob.bytes())?)
2297 }
2298
2299 #[instrument(skip_all, fields(
2305 chain_id = %self.chain_id(),
2306 block_height = %block.height
2307 ))]
2308 pub(crate) async fn stage_block_execution(
2309 &mut self,
2310 block: ProposedBlock,
2311 round: Option<u32>,
2312 published_blobs: &[Blob],
2313 policy: BundleExecutionPolicy,
2314 ) -> Result<
2315 (
2316 ProposedBlock,
2317 Block,
2318 ChainInfoResponse,
2319 ResourceTracker,
2320 HashSet<ChainId>,
2321 ),
2322 WorkerError,
2323 > {
2324 self.initialize_and_save_if_needed().await?;
2325 let local_time = self.storage.clock().current_time();
2326 let (_, committee) = self.chain.current_committee().await?;
2327 block.check_proposal_size(committee.policy().maximum_block_proposal_size)?;
2328
2329 self.chain
2330 .remove_bundles_from_inboxes(block.timestamp, true, block.incoming_bundles())
2331 .await?;
2332 let (executed_block, resource_tracker, never_reject_origins) =
2333 Box::pin(self.execute_block(
2334 block,
2335 local_time,
2336 round,
2337 published_blobs,
2338 policy,
2339 BlockExecutionPhase::StageProposal,
2340 ))
2341 .await?;
2342
2343 let info = ChainInfo::from_chain_view(&mut self.chain).await?;
2345 let mut response = ChainInfoResponse::new(info, None);
2346 if let Some(owner) = executed_block.header.authenticated_owner {
2347 response.info.requested_owner_balance = self
2348 .chain
2349 .execution_state
2350 .system
2351 .balances
2352 .get(&owner)
2353 .await?;
2354 }
2355
2356 let (proposed_block, _) = executed_block.clone().into_proposal();
2357 Ok((
2358 proposed_block,
2359 executed_block,
2360 response,
2361 resource_tracker,
2362 never_reject_origins,
2363 ))
2364 }
2365
2366 #[instrument(skip_all, fields(
2373 chain_id = %self.chain_id(),
2374 block_height = %proposal.content.block.height
2375 ))]
2376 pub(crate) async fn handle_block_proposal(
2377 &mut self,
2378 proposal: BlockProposal,
2379 ) -> (Result<ChainInfoResponse, WorkerError>, NetworkActions) {
2380 #[cfg(with_metrics)]
2381 metrics::BLOCK_PROPOSALS_RECEIVED_TOTAL.inc();
2382 let chain_id = proposal.content.block.chain_id;
2383 let height = proposal.content.block.height;
2384 let old_round = self.chain.manager.current_round();
2385 match self.try_handle_block_proposal(proposal).await {
2386 Ok((response, actions)) => (Ok(response), actions),
2387 Err(err) => {
2388 let error_type = err.error_type();
2389 #[cfg(with_metrics)]
2390 metrics::BLOCK_PROPOSALS_REJECTED_TOTAL
2391 .with_label_values(&[error_type.as_str()])
2392 .inc();
2393 debug!(%chain_id, %height, %error_type, "Block proposal rejected");
2394 let actions = if self.chain.manager.current_round() != old_round {
2399 self.create_network_actions(Some(old_round))
2400 .await
2401 .unwrap_or_default()
2402 } else {
2403 NetworkActions::default()
2404 };
2405 (Err(err), actions)
2406 }
2407 }
2408 }
2409
2410 async fn try_handle_block_proposal(
2411 &mut self,
2412 proposal: BlockProposal,
2413 ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> {
2414 self.initialize_and_save_if_needed().await?;
2415 proposal
2416 .check_invariants()
2417 .map_err(|msg| WorkerError::InvalidBlockProposal(msg.to_string()))?;
2418 proposal.check_signature()?;
2419 let owner = proposal.owner();
2420 let BlockProposal {
2421 content,
2422 original_proposal,
2423 signature: _,
2424 } = &proposal;
2425 let block = &content.block;
2426 let chain = &self.chain;
2427 chain.tip_state.get().verify_block_chaining(block)?;
2429 let (epoch, committee) = chain.current_committee().await?;
2431 check_block_epoch(epoch, block.chain_id, block.epoch)?;
2432 let policy = committee.policy().clone();
2433 block.check_proposal_size(policy.maximum_block_proposal_size)?;
2434 ensure!(
2436 chain.manager.can_propose(&owner, proposal.content.round),
2437 WorkerError::InvalidOwner
2438 );
2439 let old_round = self.chain.manager.current_round();
2440 match original_proposal {
2441 None => {
2442 if let Some(signer) = block.authenticated_owner {
2443 ensure!(signer == owner, WorkerError::InvalidSigner(owner));
2445 }
2446 }
2447 Some(OriginalProposal::Regular { certificate }) => {
2448 certificate.check(&committee)?;
2450 }
2451 Some(OriginalProposal::Fast(signature)) => {
2452 let original_proposal = BlockProposal {
2453 content: ProposalContent {
2454 block: content.block.clone(),
2455 round: Round::Fast,
2456 outcome: None,
2457 },
2458 signature: *signature,
2459 original_proposal: None,
2460 };
2461 let super_owner = original_proposal.owner();
2462 ensure!(
2463 chain
2464 .manager
2465 .ownership
2466 .get()
2467 .super_owners
2468 .contains(&super_owner),
2469 WorkerError::InvalidOwner
2470 );
2471 if let Some(signer) = block.authenticated_owner {
2472 ensure!(signer == super_owner, WorkerError::InvalidSigner(signer));
2474 }
2475 original_proposal.check_signature()?;
2476 }
2477 }
2478 let local_time = self.storage.clock().current_time();
2479 match chain.manager.check_proposed_block(&proposal) {
2480 Ok(manager::Outcome::Skip) => {
2481 return Ok((self.chain_info_response().await?, NetworkActions::default()));
2483 }
2484 Ok(manager::Outcome::Accept) => {}
2485 Err(err) => {
2486 if matches!(err, ChainError::HasIncompatibleConfirmedVote(_, _))
2494 && self
2495 .chain
2496 .manager
2497 .update_signed_proposal(&proposal, local_time)
2498 {
2499 self.save().await?;
2500 }
2501 return Err(err.into());
2502 }
2503 }
2504
2505 if self
2508 .chain
2509 .manager
2510 .update_signed_proposal(&proposal, local_time)
2511 {
2512 self.save().await?;
2513 }
2514
2515 let published_blobs = self.load_proposal_blobs(&proposal).await?;
2516 let ProposalContent {
2517 block,
2518 round,
2519 outcome,
2520 } = content;
2521
2522 if self.config.key_pair().is_some()
2523 && block.timestamp.duration_since(local_time) > self.config.block_time_grace_period
2524 {
2525 return Err(WorkerError::InvalidTimestamp {
2526 local_time,
2527 block_timestamp: block.timestamp,
2528 block_time_grace_period: self.config.block_time_grace_period,
2529 });
2530 }
2531 self.chain
2536 .remove_bundles_from_inboxes(block.timestamp, true, block.incoming_bundles())
2537 .await?;
2538 let block = if let Some(outcome) = outcome {
2539 outcome.clone().with(proposal.content.block.clone())
2540 } else {
2541 let (executed_block, _resource_tracker, _) = Box::pin(self.execute_block(
2542 block.clone(),
2543 local_time,
2544 round.multi_leader(),
2545 &published_blobs,
2546 BundleExecutionPolicy::committed(),
2547 BlockExecutionPhase::HandleProposal,
2548 ))
2549 .await?;
2550 executed_block
2551 };
2552
2553 ensure!(
2554 !round.is_fast() || !block.has_oracle_responses(),
2555 WorkerError::FastBlockUsingOracles
2556 );
2557 let chain = &mut self.chain;
2558 chain.rollback();
2560
2561 let blobs = self
2563 .get_required_blobs(proposal.expected_blob_ids(), block.created_blobs())
2564 .await?;
2565 let key_pair = self.config.key_pair();
2566 let manager = &mut self.chain.manager;
2567 match manager.create_vote(&proposal, block, key_pair, local_time, blobs)? {
2568 Some(Either::Left(vote)) => {
2570 self.block_values
2571 .insert_hashed(Cow::Borrowed(vote.value.inner()));
2572 }
2573 Some(Either::Right(vote)) => {
2574 self.block_values
2575 .insert_hashed(Cow::Borrowed(vote.value.inner()));
2576 }
2577 None => (),
2578 }
2579 self.save().await?;
2580 let actions = self.create_network_actions(Some(old_round)).await?;
2581 Ok((self.chain_info_response().await?, actions))
2582 }
2583
2584 #[instrument(skip_all, fields(
2586 chain_id = %self.chain_id()
2587 ))]
2588 async fn prepare_chain_info_response(
2589 &mut self,
2590 query: ChainInfoQuery,
2591 ) -> Result<ChainInfoResponse, WorkerError> {
2592 self.initialize_and_save_if_needed().await?;
2593 let mut info = ChainInfo::from_chain_view(&mut self.chain).await?;
2594 let chain = &self.chain;
2595 if query.request_owner_balance == AccountOwner::CHAIN {
2596 info.requested_owner_balance = Some(*chain.execution_state.system.balance.get());
2597 } else {
2598 info.requested_owner_balance = chain
2599 .execution_state
2600 .system
2601 .balances
2602 .get(&query.request_owner_balance)
2603 .await?;
2604 }
2605 if let Some(next_block_height) = query.test_next_block_height {
2606 ensure!(
2608 chain.tip_state.get().next_block_height == next_block_height,
2609 WorkerError::UnexpectedBlockHeight {
2610 expected_block_height: chain.tip_state.get().next_block_height,
2611 found_block_height: next_block_height,
2612 }
2613 );
2614 }
2615 if query.request_pending_message_bundles {
2616 let mut bundles = Vec::new();
2617 let nonempty_origins: Vec<ChainId> =
2618 chain.nonempty_inboxes.get().iter().copied().collect();
2619 #[cfg(with_metrics)]
2620 metrics::NUM_INBOXES
2621 .with_label_values(&[])
2622 .observe(nonempty_origins.len() as f64);
2623 let is_closed = *chain.execution_state.system.closed.get();
2624 let action = if is_closed {
2625 MessageAction::Reject
2626 } else {
2627 MessageAction::Accept
2628 };
2629 let inboxes = chain.inboxes.try_load_entries(&nonempty_origins).await?;
2630 for (origin, inbox) in nonempty_origins.into_iter().zip(inboxes) {
2631 let inbox = inbox.ok_or_else(|| {
2632 ChainError::InternalError(format!("Missing inbox for origin {origin}"))
2633 })?;
2634 for bundle in inbox.added_bundles.elements().await? {
2635 bundles.push(IncomingBundle {
2636 origin,
2637 bundle,
2638 action,
2639 });
2640 }
2641 }
2642 if is_closed && !bundles.is_empty() {
2643 info!(
2644 chain_id = %chain.chain_id(),
2645 count = bundles.len(),
2646 "Auto-rejecting all incoming message bundles because the chain is closed"
2647 );
2648 }
2649 info.requested_pending_message_bundles = bundles;
2650 }
2651 let hashes = chain
2652 .block_hashes_for_heights(query.request_sent_certificate_hashes_by_heights)
2653 .await?;
2654 info.requested_sent_certificate_hashes = hashes;
2655 if let Some(start) = query.request_received_log_excluding_first_n {
2656 let start = usize::try_from(start).map_err(|_| ArithmeticError::Overflow)?;
2657 let max_received_log_entries = self.config.chain_info_max_received_log_entries;
2658 let end = start
2659 .saturating_add(max_received_log_entries)
2660 .min(chain.received_log.count());
2661 info.requested_received_log = chain.received_log.read(start..end).await?;
2662 }
2663 if query.request_manager_values {
2664 info.manager.add_values(&chain.manager);
2665 }
2666 if !query.request_previous_event_blocks.is_empty() {
2667 let stream_ids = query.request_previous_event_blocks;
2668 let heights = chain
2669 .execution_state
2670 .previous_event_blocks
2671 .multi_get(&stream_ids)
2672 .await?;
2673 let mut streams_with_heights = Vec::new();
2674 for (stream_id, height) in stream_ids.into_iter().zip(heights) {
2675 if let Some(height) = height {
2676 streams_with_heights.push((stream_id, height));
2677 }
2678 }
2679 let hashes = chain
2680 .block_hashes
2681 .multi_get(streams_with_heights.iter().map(|(_, height)| height))
2682 .await?;
2683 for (maybe_hash, (stream_id, height)) in hashes.into_iter().zip(streams_with_heights) {
2684 let hash = maybe_hash.ok_or_else(|| WorkerError::BlockHashNotFound {
2685 height,
2686 chain_id: info.chain_id,
2687 })?;
2688 info.requested_previous_event_blocks
2689 .insert(stream_id, (height, hash));
2690 }
2691 }
2692 if query.request_latest_checkpoint_height {
2693 info.requested_latest_checkpoint_height = *self.chain.latest_checkpoint_height.get();
2694 }
2695 Ok(ChainInfoResponse::new(info, self.config.key_pair()))
2696 }
2697
2698 #[instrument(skip_all, fields(
2702 chain_id = %self.chain_id(),
2703 block_height = %block.height
2704 ))]
2705 async fn execute_block(
2706 &mut self,
2707 block: ProposedBlock,
2708 local_time: Timestamp,
2709 round: Option<u32>,
2710 published_blobs: &[Blob],
2711 policy: BundleExecutionPolicy,
2712 phase: BlockExecutionPhase,
2713 ) -> Result<(Block, ResourceTracker, HashSet<ChainId>), WorkerError> {
2714 let (proposed_block, outcome, resource_tracker, never_reject_origins) =
2715 Box::pin(self.chain.execute_block(
2716 block,
2717 local_time,
2718 round,
2719 published_blobs,
2720 None,
2721 policy,
2722 phase,
2723 ))
2724 .await?;
2725 let executed_block = Block::new(proposed_block, outcome);
2726 let block_hash = executed_block.hash();
2727 if let Some(cache) = &self.execution_state_cache {
2728 cache.insert(
2729 &block_hash,
2730 Box::pin(
2731 self.chain
2732 .execution_state
2733 .with_context(|ctx| InactiveContext(ctx.base_key().clone())),
2734 )
2735 .await,
2736 );
2737 }
2738 Ok((executed_block, resource_tracker, never_reject_origins))
2739 }
2740
2741 #[instrument(skip_all, fields(
2743 chain_id = %self.chain_id()
2744 ))]
2745 pub(crate) async fn initialize_and_save_if_needed(&mut self) -> Result<(), WorkerError> {
2746 if !self.knows_chain_is_active {
2747 let local_time = self.storage.clock().current_time();
2748 self.chain.initialize_if_needed(local_time).await?;
2749 self.save().await?;
2750 self.knows_chain_is_active = true;
2751 }
2752 Ok(())
2753 }
2754
2755 pub(crate) async fn chain_info_response(&mut self) -> Result<ChainInfoResponse, WorkerError> {
2756 let info = ChainInfo::from_chain_view(&mut self.chain).await?;
2757 Ok(ChainInfoResponse::new(info, self.config.key_pair()))
2758 }
2759
2760 #[instrument(skip_all, fields(
2764 chain_id = %self.chain_id()
2765 ))]
2766 pub(crate) async fn save(&mut self) -> Result<(), WorkerError> {
2767 if let Err(error) = self.chain.save().await {
2768 if error.must_reload_view() {
2769 tracing::error!(
2770 ?error,
2771 chain_id = %self.chain_id(),
2772 "Chain save failed with a nonrecoverable error; marking worker as poisoned"
2773 );
2774 self.poisoned = true;
2775 }
2776 return Err(WorkerError::ViewError(error));
2777 }
2778 Ok(())
2779 }
2780
2781 #[instrument(skip_all, fields(
2786 chain_id = %self.chain_id()
2787 ))]
2788 async fn wipe_and_reload_chain(&mut self) -> Result<(), WorkerError> {
2789 let context = self.chain.context().clone();
2790 let mut batch = Batch::new();
2791 batch.delete_key_prefix(Vec::new());
2792 if let Err(error) = context.store().write_batch(batch).await {
2793 tracing::error!(
2794 ?error,
2795 chain_id = %self.chain_id(),
2796 "Wiping chain storage failed; marking worker as poisoned"
2797 );
2798 self.poisoned = true;
2799 return Err(WorkerError::PoisonedWorker);
2800 }
2801 match ChainStateView::load(context).await {
2802 Ok(chain) => {
2803 self.chain = chain;
2804 Ok(())
2805 }
2806 Err(error) => {
2807 tracing::error!(
2808 ?error,
2809 chain_id = %self.chain_id(),
2810 "Reloading chain after wipe failed; marking worker as poisoned"
2811 );
2812 self.poisoned = true;
2813 Err(WorkerError::PoisonedWorker)
2814 }
2815 }
2816 }
2817}
2818
2819fn classify_processing_error(error: WorkerError) -> (Option<WorkerError>, WorkerError) {
2827 if error.must_reload_view() || error.indicates_corrupted_chain_state() {
2828 (Some(error), WorkerError::BatchRolledBack)
2829 } else {
2830 (None, error)
2831 }
2832}
2833
2834pub(crate) fn send_result<T>(sender: oneshot::Sender<T>, value: T) {
2837 if sender.send(value).is_err() {
2838 tracing::debug!("cannot send cross-chain result; receiver dropped");
2839 }
2840}
2841
2842fn missing_indices_blob_ids(maybe_blobs: &[(BlobId, Option<Blob>)]) -> (Vec<usize>, Vec<BlobId>) {
2844 let mut missing_indices = Vec::new();
2845 let mut missing_blob_ids = Vec::new();
2846 for (index, (blob_id, blob)) in maybe_blobs.iter().enumerate() {
2847 if blob.is_none() {
2848 missing_indices.push(index);
2849 missing_blob_ids.push(*blob_id);
2850 }
2851 }
2852 (missing_indices, missing_blob_ids)
2853}
2854
2855fn missing_blob_ids<'a>(
2857 maybe_blobs: impl IntoIterator<Item = (&'a BlobId, &'a Option<Blob>)>,
2858) -> Vec<BlobId> {
2859 maybe_blobs
2860 .into_iter()
2861 .filter(|(_, maybe_blob)| maybe_blob.is_none())
2862 .map(|(blob_id, _)| *blob_id)
2863 .collect()
2864}
2865
2866fn check_block_epoch(
2868 chain_epoch: Epoch,
2869 block_chain: ChainId,
2870 block_epoch: Epoch,
2871) -> Result<(), WorkerError> {
2872 ensure!(
2873 block_epoch == chain_epoch,
2874 WorkerError::InvalidEpoch {
2875 chain_id: block_chain,
2876 epoch: block_epoch,
2877 chain_epoch
2878 }
2879 );
2880 Ok(())
2881}