1use std::{
5 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
6 sync::Arc,
7};
8
9use allocative::Allocative;
10use linera_base::{
11 crypto::{CryptoHash, ValidatorPublicKey},
12 data_types::{
13 ApplicationDescription, ApplicationPermissions, ArithmeticError, Blob, BlockHeight, Cursor,
14 Epoch, NonCanonicalBTreeMap, NonCanonicalBTreeSet, OracleResponse, Timestamp,
15 },
16 ensure,
17 hashed::Hashed,
18 identifiers::{AccountOwner, ApplicationId, BlobType, ChainId, GenericApplicationId, StreamId},
19 ownership::ChainOwnership,
20 time::{Duration, Instant},
21};
22use linera_execution::{
23 committee::Committee, ExecutionRuntimeContext, ExecutionStateView, Message, Operation,
24 PreparedCheckpoint, Query, QueryContext, QueryOutcome, ResourceController, ResourceTracker,
25 ServiceRuntimeEndpoint, TransactionTracker,
26};
27use linera_views::{
28 context::Context,
29 log_view::LogView,
30 map_view::{CustomMapView, MapView},
31 reentrant_collection_view::{ReadGuardedView, ReentrantCollectionView},
32 register_view::RegisterView,
33 set_view::SetView,
34 views::{ClonableView, RootView, View},
35};
36use serde::{Deserialize, Serialize};
37use tracing::{info, instrument, warn};
38
39use crate::{
40 block::{Block, ConfirmedBlock},
41 block_tracker::BlockExecutionTracker,
42 data_types::{
43 BlockExecutionOutcome, BundleExecutionPolicy, BundleFailurePolicy, ChainAndHeight,
44 IncomingBundle, MessageAction, MessageBundle, ProposedBlock, Transaction,
45 },
46 inbox::{InboxError, InboxStateView},
47 manager::ChainManager,
48 outbox::OutboxStateView,
49 pending_blobs::PendingBlobsView,
50 ChainError, ChainExecutionContext, ExecutionError, ExecutionResultExt,
51};
52
53#[cfg(test)]
54#[path = "unit_tests/chain_tests.rs"]
55mod chain_tests;
56
57#[cfg(with_metrics)]
58use linera_base::prometheus_util::MeasureLatency;
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::IntoStaticStr)]
73#[strum(serialize_all = "snake_case")]
74pub enum BlockExecutionPhase {
75 StageProposal,
77 HandleProposal,
79 HandleConfirmed,
82}
83
84pub enum BlockExecution {
95 StageProposal {
98 policy: BundleExecutionPolicy,
100 },
101 HandleProposal,
104 HandleConfirmed {
107 oracle_responses: Vec<Vec<OracleResponse>>,
109 },
110}
111
112impl BlockExecution {
113 pub fn phase(&self) -> BlockExecutionPhase {
115 match self {
116 BlockExecution::StageProposal { .. } => BlockExecutionPhase::StageProposal,
117 BlockExecution::HandleProposal => BlockExecutionPhase::HandleProposal,
118 BlockExecution::HandleConfirmed { .. } => BlockExecutionPhase::HandleConfirmed,
119 }
120 }
121
122 fn into_oracle_and_policy(self) -> (Option<Vec<Vec<OracleResponse>>>, BundleExecutionPolicy) {
124 match self {
125 BlockExecution::StageProposal { policy } => (None, policy),
126 BlockExecution::HandleProposal => (None, BundleExecutionPolicy::committed()),
127 BlockExecution::HandleConfirmed { oracle_responses } => {
128 (Some(oracle_responses), BundleExecutionPolicy::committed())
129 }
130 }
131 }
132}
133
134#[cfg(with_metrics)]
135pub(crate) mod metrics {
136 use linera_base::prometheus_util::{
137 exponential_bucket_interval, register_histogram_vec, register_int_counter_vec,
138 };
139 use linera_execution::ResourceTracker;
140 use prometheus::{HistogramVec, IntCounterVec};
141
142 pub(crate) fn track_block_metrics(
144 tracker: &ResourceTracker,
145 phase: super::BlockExecutionPhase,
146 ) {
147 let phase: &[&str] = &[phase.into()];
148 NUM_BLOCKS_EXECUTED.with_label_values(phase).inc();
149 WASM_FUEL_USED_PER_BLOCK
150 .with_label_values(phase)
151 .observe(tracker.wasm_fuel as f64);
152 EVM_FUEL_USED_PER_BLOCK
153 .with_label_values(phase)
154 .observe(tracker.evm_fuel as f64);
155 VM_NUM_READS_PER_BLOCK
156 .with_label_values(phase)
157 .observe(tracker.read_operations as f64);
158 VM_BYTES_READ_PER_BLOCK
159 .with_label_values(phase)
160 .observe(tracker.bytes_read as f64);
161 VM_BYTES_WRITTEN_PER_BLOCK
162 .with_label_values(phase)
163 .observe(tracker.bytes_written as f64);
164 }
165
166 linera_base::declare_metrics! {
167 pub static NUM_BLOCKS_EXECUTED: IntCounterVec =
168 register_int_counter_vec(
169 "num_blocks_executed",
170 "Number of blocks executed",
171 &["phase"],
172 );
173
174 pub static BLOCK_EXECUTION_LATENCY: HistogramVec =
175 register_histogram_vec(
176 "block_execution_latency",
177 "Block execution latency",
178 &["phase"],
179 exponential_bucket_interval(50.0_f64, 10_000_000.0),
180 );
181
182 #[cfg(with_metrics)]
183 pub static MESSAGE_EXECUTION_LATENCY: HistogramVec =
184 register_histogram_vec(
185 "message_execution_latency",
186 "Message execution latency",
187 &["phase"],
188 exponential_bucket_interval(0.1_f64, 50_000.0),
189 );
190
191 pub static OPERATION_EXECUTION_LATENCY: HistogramVec =
192 register_histogram_vec(
193 "operation_execution_latency",
194 "Operation execution latency",
195 &["phase"],
196 exponential_bucket_interval(0.1_f64, 50_000.0),
197 );
198
199 pub static WASM_FUEL_USED_PER_BLOCK: HistogramVec =
200 register_histogram_vec(
201 "wasm_fuel_used_per_block",
202 "Wasm fuel used per block",
203 &["phase"],
204 exponential_bucket_interval(10.0, 100_000_000.0),
205 );
206
207 pub static EVM_FUEL_USED_PER_BLOCK: HistogramVec =
208 register_histogram_vec(
209 "evm_fuel_used_per_block",
210 "EVM fuel used per block",
211 &["phase"],
212 exponential_bucket_interval(10.0, 100_000_000.0),
213 );
214
215 pub static VM_NUM_READS_PER_BLOCK: HistogramVec =
216 register_histogram_vec(
217 "vm_num_reads_per_block",
218 "VM number of reads per block",
219 &["phase"],
220 exponential_bucket_interval(0.1, 100.0),
221 );
222
223 pub static VM_BYTES_READ_PER_BLOCK: HistogramVec =
224 register_histogram_vec(
225 "vm_bytes_read_per_block",
226 "VM number of bytes read per block",
227 &["phase"],
228 exponential_bucket_interval(0.1, 10_000_000.0),
229 );
230
231 pub static VM_BYTES_WRITTEN_PER_BLOCK: HistogramVec =
232 register_histogram_vec(
233 "vm_bytes_written_per_block",
234 "VM number of bytes written per block",
235 &["phase"],
236 exponential_bucket_interval(0.1, 10_000_000.0),
237 );
238
239 pub static STATE_HASH_COMPUTATION_LATENCY: HistogramVec =
240 register_histogram_vec(
241 "state_hash_computation_latency",
242 "Time to recompute the state hash, in microseconds",
243 &["phase"],
244 exponential_bucket_interval(1.0, 2_000_000.0),
245 );
246
247 pub static NUM_OUTBOXES: HistogramVec =
248 register_histogram_vec(
249 "num_outboxes",
250 "Number of outboxes",
251 &[],
252 exponential_bucket_interval(1.0, 1_000_000.0),
253 );
254
255 pub static OUTBOX_COUNTERS_SIZE: HistogramVec =
256 register_histogram_vec(
257 "outbox_counters_size",
258 "Number of entries in the outbox_counters map (in-flight message heights)",
259 &[],
260 exponential_bucket_interval(1.0, 1_000_000.0),
261 );
262 }
263}
264
265pub(crate) const EMPTY_BLOCK_SIZE: usize = 94;
267
268#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
274pub struct ChainIdSet(pub BTreeSet<ChainId>);
275
276impl linera_base::crypto::BcsHashable<'_> for ChainIdSet {}
277
278impl std::ops::Deref for ChainIdSet {
279 type Target = BTreeSet<ChainId>;
280
281 fn deref(&self) -> &Self::Target {
282 &self.0
283 }
284}
285
286#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Allocative)]
289#[cfg_attr(with_graphql, derive(async_graphql::SimpleObject))]
290pub struct StreamCounts {
291 pub first_index: u32,
296 pub next_index: u32,
299}
300
301#[cfg_attr(
303 with_graphql,
304 derive(async_graphql::SimpleObject),
305 graphql(cache_control(no_cache))
306)]
307#[derive(Debug, RootView, ClonableView, Allocative)]
308#[allocative(bound = "C")]
309pub struct ChainStateView<C>
310where
311 C: Clone + Context + 'static,
312{
313 pub execution_state: ExecutionStateView<C>,
315
316 pub tip_state: RegisterView<C, ChainTipState>,
318
319 pub manager: ChainManager<C>,
321 pub pending_validated_blobs: PendingBlobsView<C>,
324 pub pending_proposed_blobs: ReentrantCollectionView<C, AccountOwner, PendingBlobsView<C>>,
326
327 pub block_hashes: CustomMapView<C, BlockHeight, CryptoHash>,
331 pub received_log: LogView<C, ChainAndHeight>,
333 pub received_certificate_trackers: RegisterView<C, HashMap<ValidatorPublicKey, u64>>,
335
336 pub inboxes: ReentrantCollectionView<C, ChainId, InboxStateView<C>>,
338 pub outboxes: ReentrantCollectionView<C, ChainId, OutboxStateView<C>>,
340 pub next_expected_events: MapView<C, StreamId, StreamCounts>,
344 pub outbox_counters: RegisterView<C, NonCanonicalBTreeMap<BlockHeight, u32>>,
347 pub nonempty_outboxes: RegisterView<C, NonCanonicalBTreeSet<ChainId>>,
349
350 pub nonempty_inboxes: RegisterView<C, NonCanonicalBTreeSet<ChainId>>,
352
353 pub chain_initialized_at: RegisterView<C, Timestamp>,
358
359 pub next_height_to_preprocess: RegisterView<C, BlockHeight>,
368
369 pub latest_checkpoint_height: RegisterView<C, Option<BlockHeight>>,
373
374 pub pre_checkpoint_block_trust: SetView<C, CryptoHash>,
382
383 pub outbox_index_tracked_hash: RegisterView<C, Option<CryptoHash>>,
390
391 pub exported_heights: RegisterView<C, NonCanonicalBTreeMap<ValidatorPublicKey, BlockHeight>>,
397}
398
399#[cfg_attr(with_graphql, derive(async_graphql::SimpleObject))]
401#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize, Allocative)]
402pub struct ChainTipState {
403 pub block_hash: Option<CryptoHash>,
405 pub next_block_height: BlockHeight,
407}
408
409impl ChainTipState {
410 pub fn verify_block_chaining(&self, new_block: &ProposedBlock) -> Result<(), ChainError> {
413 ensure!(
414 new_block.height == self.next_block_height,
415 ChainError::UnexpectedBlockHeight {
416 expected_block_height: self.next_block_height,
417 found_block_height: new_block.height
418 }
419 );
420 ensure!(
421 new_block.previous_block_hash == self.block_hash,
422 ChainError::UnexpectedPreviousBlockHash
423 );
424 Ok(())
425 }
426
427 pub fn already_validated_block(&self, height: BlockHeight) -> Result<bool, ChainError> {
430 ensure!(
431 self.next_block_height >= height,
432 ChainError::MissingEarlierBlocks {
433 current_block_height: self.next_block_height,
434 }
435 );
436 Ok(self.next_block_height > height)
437 }
438}
439
440impl<C> ChainStateView<C>
441where
442 C: Context + Clone + 'static,
443 C::Extra: ExecutionRuntimeContext,
444{
445 pub fn chain_id(&self) -> ChainId {
447 self.context().extra().chain_id()
448 }
449
450 #[instrument(skip_all, fields(
451 chain_id = %self.chain_id(),
452 ))]
453 pub async fn query_application(
455 &mut self,
456 local_time: Timestamp,
457 query: Query,
458 service_runtime_endpoint: Option<&mut ServiceRuntimeEndpoint>,
459 ) -> Result<QueryOutcome, ChainError> {
460 let context = QueryContext {
461 chain_id: self.chain_id(),
462 next_block_height: self.tip_state.get().next_block_height,
463 local_time,
464 };
465 self.execution_state
466 .query_application(context, query, service_runtime_endpoint)
467 .await
468 .with_execution_context(ChainExecutionContext::Query)
469 }
470
471 #[instrument(skip_all, fields(
472 chain_id = %self.chain_id(),
473 application_id = %application_id
474 ))]
475 pub async fn describe_application(
477 &mut self,
478 application_id: ApplicationId,
479 ) -> Result<ApplicationDescription, ChainError> {
480 self.execution_state
481 .system
482 .describe_application(application_id, &mut TransactionTracker::default())
483 .await
484 .with_execution_context(ChainExecutionContext::DescribeApplication)
485 }
486
487 #[instrument(skip_all, fields(
488 chain_id = %self.chain_id(),
489 target = %target,
490 height = %height
491 ))]
492 pub async fn mark_messages_as_received(
495 &mut self,
496 target: &ChainId,
497 height: BlockHeight,
498 tracked: Option<&ChainIdSet>,
499 ) -> Result<bool, ChainError> {
500 let mut outbox = self.outboxes.try_load_entry_mut(target).await?;
501 let updates = outbox.mark_messages_as_received(height).await?;
502 if updates.is_empty() {
503 return Ok(false);
504 }
505 if tracked.is_none_or(|tracked| tracked.contains(target)) {
512 for update in updates {
513 let counter = self
514 .outbox_counters
515 .get_mut()
516 .get_mut(&update)
517 .ok_or_else(|| {
518 ChainError::CorruptedChainState("message counter should be present".into())
519 })?;
520 *counter = counter.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
521 if *counter == 0 {
522 self.outbox_counters.get_mut().remove(&update);
524 }
525 }
526 }
527 if outbox.queue.count() == 0 {
528 self.nonempty_outboxes.get_mut().remove(target);
529 if *outbox.next_height_to_schedule.get() <= self.tip_state.get().next_block_height {
531 self.outboxes.remove_entry(target)?;
532 }
533 }
534 #[cfg(with_metrics)]
535 metrics::NUM_OUTBOXES
536 .with_label_values(&[])
537 .observe(self.nonempty_outboxes.get().len() as f64);
538 #[cfg(with_metrics)]
539 metrics::OUTBOX_COUNTERS_SIZE
540 .with_label_values(&[])
541 .observe(self.outbox_counters.get().len() as f64);
542 Ok(true)
543 }
544
545 pub fn all_messages_delivered_up_to(&self, height: BlockHeight) -> bool {
548 tracing::debug!(
549 "Messages left in {:.8}'s outbox: {:?}",
550 self.chain_id(),
551 self.outbox_counters.get()
552 );
553 if let Some((key, _)) = self.outbox_counters.get().first_key_value() {
554 key > &height
555 } else {
556 true
557 }
558 }
559
560 pub async fn is_active(&self) -> Result<bool, ChainError> {
562 Ok(self.execution_state.system.is_active().await?)
563 }
564
565 pub async fn initialize_if_needed(&mut self, local_time: Timestamp) -> Result<(), ChainError> {
567 let chain_id = self.chain_id();
568 if self
570 .execution_state
571 .system
572 .initialize_chain(chain_id)
573 .await
574 .with_execution_context(ChainExecutionContext::Block)?
575 {
576 return Ok(());
578 }
579 let maybe_committee = self
580 .execution_state
581 .system
582 .current_committee()
583 .await
584 .with_execution_context(ChainExecutionContext::Block)?;
585 self.manager.reset(
587 self.execution_state.system.ownership.get().await?.clone(),
588 BlockHeight(0),
589 local_time,
590 maybe_committee
591 .iter()
592 .flat_map(|(_, committee)| committee.account_keys_and_weights()),
593 )?;
594 Ok(())
595 }
596
597 fn insert_block_hash(
601 &mut self,
602 height: BlockHeight,
603 hash: CryptoHash,
604 ) -> Result<(), ChainError> {
605 self.block_hashes.insert(&height, hash)?;
606 if *self.next_height_to_preprocess.get() <= height {
607 self.next_height_to_preprocess.set(height.try_add_one()?);
608 }
609 Ok(())
610 }
611
612 #[instrument(skip_all, fields(
619 chain_id = %self.chain_id(),
620 origin = %origin,
621 bundle_height = %bundle.height
622 ))]
623 pub async fn receive_message_bundle_with_inbox(
624 &mut self,
625 inbox: &mut InboxStateView<C>,
626 origin: &ChainId,
627 bundle: MessageBundle,
628 local_time: Timestamp,
629 add_to_received_log: bool,
630 ) -> Result<(), ChainError> {
631 assert!(!bundle.messages.is_empty());
632 let chain_id = self.chain_id();
633 tracing::trace!(
634 "Processing new messages from {origin} at height {}",
635 bundle.height,
636 );
637 let chain_and_height = ChainAndHeight {
638 chain_id: *origin,
639 height: bundle.height,
640 };
641
642 match self.initialize_if_needed(local_time).await {
643 Ok(_) => (),
644 Err(ChainError::ExecutionError(exec_err, _))
647 if matches!(*exec_err, ExecutionError::BlobsNotFound(ref blobs)
648 if blobs.iter().all(|blob_id| {
649 blob_id.blob_type == BlobType::ChainDescription && blob_id.hash == chain_id.0
650 })) => {}
651 err => {
652 return err;
653 }
654 }
655
656 let newly_added = inbox
658 .add_bundle(bundle)
659 .await
660 .map_err(|error| match error {
661 InboxError::ViewError(error) => ChainError::ViewError(error),
662 error => ChainError::CorruptedChainState(format!(
663 "while processing messages in certified block: {error}"
664 )),
665 })?;
666 if newly_added {
667 self.nonempty_inboxes.get_mut().insert(*origin);
668 }
669
670 if add_to_received_log {
672 self.received_log.push(chain_and_height);
673 }
674 Ok(())
675 }
676
677 pub fn update_received_certificate_trackers(
679 &mut self,
680 new_trackers: BTreeMap<ValidatorPublicKey, u64>,
681 ) {
682 for (name, tracker) in new_trackers {
683 self.received_certificate_trackers
684 .get_mut()
685 .entry(name)
686 .and_modify(|t| {
687 if tracker > *t {
690 *t = tracker;
691 }
692 })
693 .or_insert(tracker);
694 }
695 }
696
697 pub async fn current_committee(&self) -> Result<(Epoch, Arc<Committee>), ChainError> {
699 let chain_id = self.chain_id();
700 self.execution_state
701 .system
702 .current_committee()
703 .await
704 .with_execution_context(ChainExecutionContext::Block)?
705 .ok_or(ChainError::InactiveChain(chain_id))
706 }
707
708 pub async fn ownership(&self) -> Result<&ChainOwnership, ChainError> {
710 Ok(self.execution_state.system.ownership.get().await?)
711 }
712
713 #[instrument(skip_all, fields(
719 chain_id = %self.chain_id(),
720 ))]
721 pub async fn remove_bundles_from_inboxes(
722 &mut self,
723 timestamp: Timestamp,
724 must_be_present: bool,
725 incoming_bundles: impl IntoIterator<Item = &IncomingBundle>,
726 ) -> Result<(), ChainError> {
727 let chain_id = self.chain_id();
728 let mut bundles_by_origin: BTreeMap<_, Vec<&MessageBundle>> = Default::default();
729 for IncomingBundle { bundle, origin, .. } in incoming_bundles {
730 ensure!(
731 bundle.timestamp <= timestamp,
732 ChainError::IncorrectBundleTimestamp {
733 chain_id,
734 bundle_timestamp: bundle.timestamp,
735 block_timestamp: timestamp,
736 }
737 );
738 let bundles = bundles_by_origin.entry(*origin).or_default();
739 bundles.push(bundle);
740 }
741 let origins = bundles_by_origin.keys().copied().collect::<Vec<_>>();
742 let inboxes = self.inboxes.try_load_entries_mut(&origins).await?;
743 let mut missing_bundles = Vec::new();
747 for ((origin, bundles), mut inbox) in bundles_by_origin.into_iter().zip(inboxes) {
748 tracing::trace!(
749 "Removing [{}] from inbox for {origin}",
750 bundles
751 .iter()
752 .map(|bundle| bundle.height.to_string())
753 .collect::<Vec<_>>()
754 .join(", ")
755 );
756 for bundle in bundles {
757 let was_present = inbox
759 .remove_bundle(bundle)
760 .await
761 .map_err(|error| (chain_id, origin, error))?;
762 if must_be_present && !was_present {
763 missing_bundles.push((origin, bundle.height));
764 }
765 }
766 inbox.observe_size_metric();
767 if inbox.added_bundles.count() == 0 {
768 self.nonempty_inboxes.get_mut().remove(&origin);
769 }
770 }
771 ensure!(
772 missing_bundles.is_empty(),
773 ChainError::MissingCrossChainUpdates {
774 chain_id,
775 bundles: missing_bundles,
776 }
777 );
778 Ok(())
779 }
780
781 pub fn nonempty_outbox_chain_ids(&self) -> Vec<ChainId> {
783 self.nonempty_outboxes.get().iter().copied().collect()
784 }
785
786 pub async fn load_outboxes(
788 &self,
789 targets: &[ChainId],
790 ) -> Result<Vec<ReadGuardedView<OutboxStateView<C>>>, ChainError> {
791 let vec_of_options = self.outboxes.try_load_entries(targets).await?;
792 let optional_vec = vec_of_options.into_iter().collect::<Option<Vec<_>>>();
793 optional_vec.ok_or_else(|| ChainError::CorruptedChainState("Missing outboxes".into()))
794 }
795
796 pub async fn reconcile_outbox_index(
802 &mut self,
803 tracked: Option<&Hashed<ChainIdSet>>,
804 ) -> Result<bool, ChainError> {
805 if *self.outbox_index_tracked_hash.get() == tracked.map(|tracked| tracked.hash()) {
806 return Ok(false);
807 }
808 self.rebuild_outbox_index(tracked).await?;
809 Ok(true)
810 }
811
812 async fn rebuild_outbox_index(
818 &mut self,
819 tracked: Option<&Hashed<ChainIdSet>>,
820 ) -> Result<(), ChainError> {
821 self.nonempty_outboxes.get_mut().clear();
822 self.outbox_counters.get_mut().clear();
823 let targets = match tracked {
826 Some(tracked) => tracked.inner().iter().copied().collect::<Vec<_>>(),
827 None => self.outboxes.indices().await?,
828 };
829 for target in &targets {
830 let heights = {
831 let Some(outbox) = self.outboxes.try_load_entry(target).await? else {
832 continue;
833 };
834 outbox.queue.elements().await?
835 };
836 if heights.is_empty() {
837 continue;
838 }
839 for height in heights {
840 *self.outbox_counters.get_mut().entry(height).or_default() += 1;
841 }
842 self.nonempty_outboxes.get_mut().insert(*target);
843 }
844 self.outbox_index_tracked_hash
845 .set(tracked.map(|tracked| tracked.hash()));
846 Ok(())
847 }
848
849 pub fn outbox_index_is_reconciled(&self, tracked: Option<&Hashed<ChainIdSet>>) -> bool {
852 *self.outbox_index_tracked_hash.get() == tracked.map(|tracked| tracked.hash())
853 }
854
855 #[expect(clippy::too_many_arguments)]
857 #[instrument(skip_all, fields(
858 chain_id = %block.chain_id,
859 block_height = %block.height
860 ))]
861 async fn execute_block_inner(
862 chain: &mut ExecutionStateView<C>,
863 block_hashes: &CustomMapView<C, BlockHeight, CryptoHash>,
864 block: &mut ProposedBlock,
865 local_time: Timestamp,
866 round: Option<u32>,
867 published_blobs: &[Blob],
868 replaying_oracle_responses: Option<Vec<Vec<OracleResponse>>>,
869 exec_policy: BundleExecutionPolicy,
870 phase: BlockExecutionPhase,
871 checkpoint_origin_cursors: Vec<(ChainId, Cursor)>,
872 checkpoint_inbox_cursors: Vec<(ChainId, Cursor)>,
873 checkpoint_outbox_block_hashes: Vec<CryptoHash>,
874 ) -> Result<(BlockExecutionOutcome, ResourceTracker, HashSet<ChainId>), ChainError> {
875 #[cfg(with_metrics)]
876 let block_execution_latency =
877 metrics::BLOCK_EXECUTION_LATENCY.with_label_values(&[phase.into()]);
878 #[cfg(with_metrics)]
879 let _execution_latency = block_execution_latency.measure_latency_us();
880
881 let committee_policy = chain
886 .system
887 .current_committee()
888 .await
889 .with_execution_context(ChainExecutionContext::Block)?
890 .ok_or_else(|| ChainError::InactiveChain(block.chain_id))?
891 .1
892 .policy()
893 .clone();
894
895 let prepared_checkpoint = if block.starts_with_checkpoint() {
904 let blobs = chain
905 .prepare_checkpoint(committee_policy.maximum_blob_size)
906 .await
907 .with_execution_context(ChainExecutionContext::Block)?;
908 Some(PreparedCheckpoint {
909 blobs,
910 origin_cursors: checkpoint_origin_cursors,
911 inbox_cursors: checkpoint_inbox_cursors,
912 outbox_block_hashes: checkpoint_outbox_block_hashes,
913 })
914 } else {
915 None
916 };
917
918 chain.system.progress.get_mut().timestamp = block.timestamp;
919
920 let start_epoch = *chain.system.epoch.get();
921
922 let mut resource_controller = ResourceController::new(
923 Arc::new(committee_policy),
924 ResourceTracker::default(),
925 block.authenticated_owner,
926 );
927
928 for blob in published_blobs {
929 let blob_id = blob.id();
930 resource_controller
931 .policy()
932 .check_blob_size(blob.content())
933 .with_execution_context(ChainExecutionContext::Block)?;
934 chain.system.used_blobs.insert(&blob_id)?;
935 }
936
937 let mut block_execution_tracker = BlockExecutionTracker::new(
938 &mut resource_controller,
939 published_blobs
940 .iter()
941 .map(|blob| (blob.id(), blob))
942 .collect(),
943 local_time,
944 replaying_oracle_responses,
945 block,
946 phase,
947 )?;
948 if let Some(prepared) = prepared_checkpoint {
949 block_execution_tracker.set_prepared_checkpoint(prepared);
950 }
951
952 let (max_failures, never_reject_application_ids) = match &exec_policy.on_failure {
954 BundleFailurePolicy::Abort => (0, Arc::new(HashSet::new())),
955 BundleFailurePolicy::AutoRetry {
956 max_failures,
957 never_reject_application_ids,
958 } => (*max_failures, never_reject_application_ids.clone()),
959 };
960 let auto_retry = !matches!(exec_policy.on_failure, BundleFailurePolicy::Abort);
961 let mut failure_count = 0u32;
962 let mut never_reject_discarded_origins = HashSet::new();
963
964 let time_budget = exec_policy.time_budget;
965 let mut cumulative_bundle_time = Duration::ZERO;
966
967 let mut i = 0;
968 while i < block.transactions.len() {
969 let transaction = &mut block.transactions[i];
970 let is_bundle = matches!(transaction, Transaction::ReceiveMessages(_));
971 let is_stream_update = transaction.is_update_stream();
972
973 if is_bundle && time_budget.is_some_and(|budget| cumulative_bundle_time >= budget) {
975 info!(
976 ?cumulative_bundle_time,
977 ?time_budget,
978 "Time budget for bundle staging exceeded, discarding remaining bundles"
979 );
980 Self::discard_remaining_bundles(block, i, None);
981 continue;
982 }
983
984 let checkpoint = if auto_retry && (is_bundle || is_stream_update) {
985 Some((
986 chain.clone_unchecked()?,
987 block_execution_tracker.create_checkpoint(),
988 ))
989 } else {
990 None
991 };
992
993 let bundle_start = if is_bundle && time_budget.is_some() {
994 Some(Instant::now())
995 } else {
996 None
997 };
998
999 let result = block_execution_tracker
1000 .execute_transaction(&*transaction, round, chain)
1001 .await;
1002
1003 if let Some(start) = bundle_start {
1004 cumulative_bundle_time += start.elapsed();
1005 }
1006
1007 match (result, transaction, checkpoint) {
1012 (Ok(()), _, _) => {
1013 i += 1;
1014 }
1015 (
1016 Err(ChainError::ExecutionError(error, _context)),
1017 Transaction::ReceiveMessages(incoming_bundle),
1018 Some((saved_chain, saved_tracker)),
1019 ) if !error.is_transient_error() && error.is_limit_error() && i > 0 => {
1020 *chain = saved_chain;
1022 block_execution_tracker.restore_checkpoint(&saved_tracker);
1023 failure_count += 1;
1024 let maybe_sender = if failure_count > max_failures {
1026 info!(
1027 failure_count,
1028 max_failures,
1029 "Exceeded max bundle failures, discarding all remaining message \
1030 bundles and stream updates"
1031 );
1032 Self::discard_remaining_stream_updates(block, i);
1033 None
1034 } else {
1035 info!(
1037 %error,
1038 index = i,
1039 origin = %incoming_bundle.origin,
1040 "Message bundle exceeded block limits and will be discarded for \
1041 retry in a later block"
1042 );
1043 Some(incoming_bundle.origin)
1044 };
1045 Self::discard_remaining_bundles(block, i, maybe_sender);
1046 }
1048 (
1049 Err(ChainError::ExecutionError(error, context)),
1050 Transaction::ReceiveMessages(incoming_bundle),
1051 Some((saved_chain, saved_tracker)),
1052 ) if !error.is_transient_error() => {
1053 *chain = saved_chain;
1055 block_execution_tracker.restore_checkpoint(&saved_tracker);
1056
1057 let all_messages_never_reject = !never_reject_application_ids.is_empty()
1058 && incoming_bundle.messages().all(|posted_msg| {
1059 never_reject_application_ids
1060 .contains(&posted_msg.message.application_id())
1061 });
1062 if (all_messages_never_reject || incoming_bundle.bundle.is_protected())
1063 && incoming_bundle.action != MessageAction::Reject
1064 {
1065 let origin = incoming_bundle.origin;
1066 never_reject_discarded_origins.insert(origin);
1067 warn!(
1068 %error,
1069 index = i,
1070 %origin,
1071 "Message bundle cannot be rejected (protected or never-reject); \
1072 discarding the bundle (and same-sender subsequent bundles) for retry \
1073 in a later block"
1074 );
1075 Self::discard_remaining_bundles(block, i, Some(origin));
1076 } else if incoming_bundle.action == MessageAction::Reject {
1077 return Err(ChainError::ExecutionError(error, context));
1079 } else {
1080 info!(
1083 %error,
1084 index = i,
1085 origin = %incoming_bundle.origin,
1086 "Message bundle failed to execute and will be rejected"
1087 );
1088 incoming_bundle.action = MessageAction::Reject;
1089 }
1090 }
1092 (
1093 Err(ChainError::ExecutionError(error, _context)),
1094 transaction,
1095 Some((saved_chain, saved_tracker)),
1096 ) if transaction.is_update_stream()
1097 && !error.is_transient_error()
1098 && error.is_limit_error()
1099 && i > 0 =>
1100 {
1101 *chain = saved_chain;
1103 block_execution_tracker.restore_checkpoint(&saved_tracker);
1104 failure_count += 1;
1105 if failure_count > max_failures {
1106 info!(
1107 failure_count,
1108 max_failures,
1109 "Exceeded max failures, discarding all remaining stream updates and \
1110 message bundles"
1111 );
1112 Self::discard_remaining_bundles(block, i, None);
1113 Self::discard_remaining_stream_updates(block, i);
1114 } else {
1115 info!(
1116 %error,
1117 index = i,
1118 "UpdateStream exceeded block limits, discarding for retry"
1119 );
1120 block.transactions.remove(i);
1121 }
1122 }
1124 (Err(e), _, _) => return Err(e),
1125 };
1126 }
1127
1128 ensure!(!block.transactions.is_empty(), ChainError::EmptyBlock);
1131
1132 let end_epoch = *chain.system.epoch.get();
1135 ensure!(
1136 end_epoch.0 <= start_epoch.0.saturating_add(1),
1137 ChainError::MultipleEpochAdvances {
1138 start_epoch,
1139 end_epoch,
1140 }
1141 );
1142
1143 let recipients = block_execution_tracker.recipients();
1144 let non_ack_tx_indices = block_execution_tracker.non_checkpoint_ack_tx_indices();
1145 let mut recipient_heights = Vec::new();
1146 for (recipient, height) in chain
1147 .previous_message_blocks
1148 .multi_get_pairs(recipients)
1149 .await?
1150 {
1151 if let Some(tx_indices) = non_ack_tx_indices.get(&recipient) {
1159 chain
1160 .previous_message_blocks
1161 .insert(&recipient, block.height)?;
1162 let mut cursors = chain
1168 .system
1169 .unfinalized_message_blocks
1170 .get(&recipient)
1171 .await?
1172 .unwrap_or_default();
1173 for index in tx_indices {
1174 cursors.insert(Cursor {
1175 height: block.height,
1176 index: *index,
1177 });
1178 }
1179 chain
1180 .system
1181 .unfinalized_message_blocks
1182 .insert(&recipient, cursors)?;
1183 }
1184 if let Some(height) = height {
1185 recipient_heights.push((recipient, height));
1186 }
1187 }
1188 let hashes = block_hashes
1189 .multi_get(recipient_heights.iter().map(|(_, height)| height))
1190 .await?;
1191 let mut previous_message_blocks = BTreeMap::new();
1192 for (hash, (recipient, height)) in hashes.into_iter().zip(recipient_heights) {
1193 let hash = hash.ok_or_else(|| {
1194 ChainError::CorruptedChainState("missing entry in block_hashes".into())
1195 })?;
1196 previous_message_blocks.insert(recipient, (hash, height));
1197 }
1198
1199 let streams = block_execution_tracker.event_streams();
1200 let mut stream_heights = Vec::new();
1201 for (stream, height) in chain.previous_event_blocks.multi_get_pairs(streams).await? {
1202 chain.previous_event_blocks.insert(&stream, block.height)?;
1203 if let Some(height) = height {
1204 stream_heights.push((stream, height));
1205 }
1206 }
1207 let hashes = block_hashes
1208 .multi_get(stream_heights.iter().map(|(_, height)| height))
1209 .await?;
1210 let mut previous_event_blocks = BTreeMap::new();
1211 for (hash, (stream, height)) in hashes.into_iter().zip(stream_heights) {
1212 let hash = hash.ok_or_else(|| {
1213 ChainError::CorruptedChainState("missing entry in block_hashes".into())
1214 })?;
1215 previous_event_blocks.insert(stream, (hash, height));
1216 }
1217
1218 let state_hash = {
1219 #[cfg(with_metrics)]
1220 let state_hash_latency =
1221 metrics::STATE_HASH_COMPUTATION_LATENCY.with_label_values(&[phase.into()]);
1222 #[cfg(with_metrics)]
1223 let _hash_latency = state_hash_latency.measure_latency_us();
1224 chain.crypto_hash_mut().await?
1225 };
1226
1227 let (messages, oracle_responses, events, blobs, operation_results, resource_tracker) =
1228 block_execution_tracker.finalize(block.transactions.len());
1229
1230 Ok((
1231 BlockExecutionOutcome {
1232 messages,
1233 previous_message_blocks,
1234 previous_event_blocks,
1235 state_hash,
1236 oracle_responses,
1237 events,
1238 blobs,
1239 operation_results,
1240 },
1241 resource_tracker,
1242 never_reject_discarded_origins,
1243 ))
1244 }
1245
1246 fn discard_remaining_stream_updates(block: &mut ProposedBlock, mut index: usize) {
1247 while index < block.transactions.len() {
1248 if block.transactions[index].is_update_stream() {
1249 block.transactions.remove(index);
1250 } else {
1251 index += 1;
1252 }
1253 }
1254 }
1255
1256 fn discard_remaining_bundles(
1257 block: &mut ProposedBlock,
1258 mut index: usize,
1259 maybe_origin: Option<ChainId>,
1260 ) {
1261 while index < block.transactions.len() {
1262 if matches!(
1263 &block.transactions[index],
1264 Transaction::ReceiveMessages(bundle)
1265 if maybe_origin.is_none_or(|origin| bundle.origin == origin)
1266 ) {
1267 block.transactions.remove(index);
1268 } else {
1269 index += 1;
1270 }
1271 }
1272 }
1273
1274 #[instrument(skip_all, fields(
1285 chain_id = %self.chain_id(),
1286 block_height = %block.height
1287 ))]
1288 pub async fn execute_block(
1289 &mut self,
1290 mut block: ProposedBlock,
1291 local_time: Timestamp,
1292 round: Option<u32>,
1293 published_blobs: &[Blob],
1294 execution: BlockExecution,
1295 ) -> Result<
1296 (
1297 ProposedBlock,
1298 BlockExecutionOutcome,
1299 ResourceTracker,
1300 HashSet<ChainId>,
1301 ),
1302 ChainError,
1303 > {
1304 assert_eq!(
1305 block.chain_id,
1306 self.execution_state.context().extra().chain_id()
1307 );
1308
1309 self.initialize_if_needed(local_time).await?;
1310
1311 let chain_timestamp = self.execution_state.system.progress.get().timestamp;
1312 ensure!(
1313 chain_timestamp <= block.timestamp,
1314 ChainError::InvalidBlockTimestamp {
1315 parent: chain_timestamp,
1316 new: block.timestamp
1317 }
1318 );
1319 ensure!(!block.transactions.is_empty(), ChainError::EmptyBlock);
1320
1321 ensure!(
1322 block.published_blob_ids()
1323 == published_blobs
1324 .iter()
1325 .map(|blob| blob.id())
1326 .collect::<BTreeSet<_>>(),
1327 ChainError::InternalError("published_blobs mismatch".to_string())
1328 );
1329
1330 if *self.execution_state.system.closed.get() {
1331 ensure!(block.has_only_rejected_messages(), ChainError::ClosedChain);
1332 }
1333
1334 Self::check_app_permissions(
1335 self.execution_state
1336 .system
1337 .application_permissions
1338 .get()
1339 .await?,
1340 &block,
1341 )?;
1342
1343 ensure!(
1344 !block
1345 .transactions
1346 .iter()
1347 .skip(1)
1348 .any(Transaction::is_checkpoint),
1349 ChainError::CheckpointPreconditionFailed(
1350 "Checkpoint must be the first transaction in its block",
1351 )
1352 );
1353 let (origin_cursors, inbox_cursors, outbox_block_hashes) = if block.starts_with_checkpoint()
1354 {
1355 self.check_checkpoint_preconditions().await?;
1356 let origin_cursors = self.collect_inbox_cursors().await?;
1357 let inbox_cursors = self.collect_all_inbox_cursors().await?;
1358 let hashes = self.collect_unfinalized_block_hashes().await?;
1359 (origin_cursors, inbox_cursors, hashes)
1360 } else {
1361 (Vec::new(), Vec::new(), Vec::new())
1362 };
1363
1364 let phase = execution.phase();
1365 let (replaying_oracle_responses, policy) = execution.into_oracle_and_policy();
1366 let (outcome, tracker, never_reject_origins) = Self::execute_block_inner(
1367 &mut self.execution_state,
1368 &self.block_hashes,
1369 &mut block,
1370 local_time,
1371 round,
1372 published_blobs,
1373 replaying_oracle_responses,
1374 policy,
1375 phase,
1376 origin_cursors,
1377 inbox_cursors,
1378 outbox_block_hashes,
1379 )
1380 .await?;
1381
1382 Ok((block, outcome, tracker, never_reject_origins))
1383 }
1384
1385 async fn collect_inbox_cursors(&self) -> Result<Vec<(ChainId, Cursor)>, ChainError> {
1393 let targets = self
1394 .execution_state
1395 .system
1396 .pending_checkpoint_ack_targets
1397 .indices()
1398 .await?;
1399 let mut cursors = Vec::with_capacity(targets.len());
1400 for origin in targets {
1401 let Some(inbox) = self.inboxes.try_load_entry(&origin).await? else {
1402 continue;
1403 };
1404 cursors.push((origin, *inbox.next_cursor_to_remove.get()));
1405 }
1406 Ok(cursors)
1407 }
1408
1409 async fn collect_all_inbox_cursors(&self) -> Result<Vec<(ChainId, Cursor)>, ChainError> {
1414 let origins = self.inboxes.indices().await?;
1415 let mut cursors = Vec::new();
1416 for origin in origins {
1417 let Some(inbox) = self.inboxes.try_load_entry(&origin).await? else {
1418 continue;
1419 };
1420 let cursor = *inbox.next_cursor_to_remove.get();
1421 if cursor != Cursor::default() {
1422 cursors.push((origin, cursor));
1423 }
1424 }
1425 Ok(cursors)
1426 }
1427
1428 pub async fn restore_outboxes_from_unfinalized(
1436 &mut self,
1437 tracked: Option<&Hashed<ChainIdSet>>,
1438 ) -> Result<(), ChainError> {
1439 let prior_recipients = self.outboxes.indices().await?;
1445 for recipient in prior_recipients {
1446 let mut outbox = self.outboxes.try_load_entry_mut(&recipient).await?;
1447 outbox.queue.clear();
1448 outbox.next_height_to_schedule.set(BlockHeight::ZERO);
1449 }
1450 let entries = self
1451 .execution_state
1452 .system
1453 .unfinalized_message_blocks
1454 .index_values()
1455 .await?;
1456 for (recipient, cursors) in entries {
1457 if cursors.is_empty() {
1458 continue;
1459 }
1460 let heights = cursors
1463 .into_iter()
1464 .map(|cursor| cursor.height)
1465 .collect::<BTreeSet<_>>();
1466 let mut outbox = self.outboxes.try_load_entry_mut(&recipient).await?;
1467 for height in &heights {
1468 outbox.queue.push_back(*height);
1469 }
1470 let max_height = *heights
1471 .last()
1472 .expect("the empty case was filtered out above");
1473 outbox
1474 .next_height_to_schedule
1475 .set(max_height.try_add_one()?);
1476 }
1477 self.rebuild_outbox_index(tracked).await?;
1479 Ok(())
1480 }
1481
1482 async fn collect_unfinalized_block_hashes(&self) -> Result<Vec<CryptoHash>, ChainError> {
1488 let heights = self.collect_unfinalized_heights().await?;
1489 let mut hashes = Vec::with_capacity(heights.len());
1490 for height in heights {
1491 let hash = self.block_hashes.get(&height).await?.ok_or_else(|| {
1492 ChainError::CorruptedChainState(format!(
1493 "missing entry in block_hashes at height {height}"
1494 ))
1495 })?;
1496 hashes.push(hash);
1497 }
1498 Ok(hashes)
1499 }
1500
1501 pub async fn collect_unfinalized_heights(&self) -> Result<BTreeSet<BlockHeight>, ChainError> {
1508 let mut heights = BTreeSet::new();
1509 let entries = self
1510 .execution_state
1511 .system
1512 .unfinalized_message_blocks
1513 .index_values()
1514 .await?;
1515 for (_, per_recipient) in entries {
1516 heights.extend(per_recipient.into_iter().map(|cursor| cursor.height));
1517 }
1518 Ok(heights)
1519 }
1520
1521 #[instrument(skip_all, fields(
1525 chain_id = %self.chain_id(),
1526 block_height = %block.inner().inner().header.height
1527 ))]
1528 pub async fn apply_confirmed_block(
1529 &mut self,
1530 block: &ConfirmedBlock,
1531 local_time: Timestamp,
1532 tracked: Option<&ChainIdSet>,
1533 ) -> Result<BTreeSet<StreamId>, ChainError> {
1534 let hash = block.inner().hash();
1535 let block = block.inner().inner();
1536 if block.header.height == BlockHeight::ZERO {
1537 self.chain_initialized_at.set(local_time);
1538 }
1539 let updated_streams = self.process_emitted_events(block).await?;
1540 self.process_outgoing_messages(block, tracked).await?;
1541
1542 self.reset_chain_manager(block.header.height.try_add_one()?, local_time)
1544 .await?;
1545
1546 let tip = self.tip_state.get_mut();
1548 tip.block_hash = Some(hash);
1549 tip.next_block_height.try_add_assign_one()?;
1550 self.insert_block_hash(block.header.height, hash)?;
1551 if block.body.starts_with_checkpoint() {
1552 self.latest_checkpoint_height.set(Some(block.header.height));
1553 }
1554 Ok(updated_streams)
1555 }
1556
1557 #[instrument(skip_all, fields(
1560 chain_id = %self.chain_id(),
1561 block_height = %block.inner().inner().header.height
1562 ))]
1563 pub async fn preprocess_block(
1564 &mut self,
1565 block: &ConfirmedBlock,
1566 tracked: Option<&ChainIdSet>,
1567 ) -> Result<BTreeSet<StreamId>, ChainError> {
1568 let hash = block.inner().hash();
1569 let block = block.inner().inner();
1570 let height = block.header.height;
1571 if height < self.tip_state.get().next_block_height {
1572 return Ok(BTreeSet::new());
1573 }
1574 self.process_outgoing_messages(block, tracked).await?;
1575 let updated_streams = self.process_emitted_events(block).await?;
1576 self.insert_block_hash(height, hash)?;
1577 Ok(updated_streams)
1578 }
1579
1580 #[instrument(skip_all, fields(
1582 block_height = %block.height,
1583 num_transactions = %block.transactions.len()
1584 ))]
1585 fn check_app_permissions(
1586 app_permissions: &ApplicationPermissions,
1587 block: &ProposedBlock,
1588 ) -> Result<(), ChainError> {
1589 let mut mandatory = app_permissions
1590 .mandatory_applications
1591 .iter()
1592 .copied()
1593 .collect::<HashSet<ApplicationId>>();
1594 for transaction in &block.transactions {
1595 match transaction {
1596 Transaction::ExecuteOperation(operation)
1597 if operation.is_exempt_from_permissions() =>
1598 {
1599 mandatory.clear()
1600 }
1601 Transaction::ExecuteOperation(operation) => {
1602 ensure!(
1603 app_permissions.can_execute_operations(&operation.application_id()),
1604 ChainError::AuthorizedApplications(
1605 app_permissions.execute_operations.clone().unwrap()
1606 )
1607 );
1608 if let Operation::User { application_id, .. } = operation {
1609 mandatory.remove(application_id);
1610 }
1611 }
1612 Transaction::ReceiveMessages(incoming_bundle)
1613 if incoming_bundle.action == MessageAction::Accept =>
1614 {
1615 for pending in incoming_bundle.messages() {
1616 if let Message::User { application_id, .. } = &pending.message {
1617 mandatory.remove(application_id);
1618 }
1619 }
1620 }
1621 Transaction::ReceiveMessages(_) => {}
1622 }
1623 }
1624 ensure!(
1625 mandatory.is_empty(),
1626 ChainError::MissingMandatoryApplications(mandatory.into_iter().collect())
1627 );
1628 Ok(())
1629 }
1630
1631 async fn check_checkpoint_preconditions(&self) -> Result<(), ChainError> {
1639 let mut had_system_event_tracker = false;
1640 self.next_expected_events
1641 .for_each_index_while(|stream_id| {
1642 if matches!(stream_id.application_id, GenericApplicationId::System) {
1643 had_system_event_tracker = true;
1644 Ok(false)
1645 } else {
1646 Ok(true)
1647 }
1648 })
1649 .await?;
1650 ensure!(
1651 !had_system_event_tracker,
1652 ChainError::CheckpointPreconditionFailed("chain has consumed system events")
1653 );
1654
1655 Ok(())
1656 }
1657
1658 #[instrument(skip_all, fields(
1661 chain_id = %self.chain_id(),
1662 next_block_height = %self.tip_state.get().next_block_height,
1663 ))]
1664 pub async fn block_hashes_for_heights(
1665 &self,
1666 heights: impl IntoIterator<Item = BlockHeight>,
1667 ) -> Result<Vec<CryptoHash>, ChainError> {
1668 let heights = heights.into_iter().collect::<Vec<_>>();
1669 Ok(self
1670 .block_hashes
1671 .multi_get(&heights)
1672 .await?
1673 .into_iter()
1674 .flatten()
1675 .collect())
1676 }
1677
1678 async fn reset_chain_manager(
1680 &mut self,
1681 next_height: BlockHeight,
1682 local_time: Timestamp,
1683 ) -> Result<(), ChainError> {
1684 let maybe_committee = self
1685 .execution_state
1686 .system
1687 .current_committee()
1688 .await
1689 .with_execution_context(ChainExecutionContext::Block)?;
1690 let ownership = self.execution_state.system.ownership.get().await?.clone();
1691 let fallback_owners = maybe_committee
1692 .iter()
1693 .flat_map(|(_, committee)| committee.account_keys_and_weights());
1694 self.pending_validated_blobs.clear();
1695 self.pending_proposed_blobs.clear();
1696 self.manager
1697 .reset(ownership, next_height, local_time, fallback_owners)
1698 }
1699
1700 #[instrument(skip_all, fields(
1704 chain_id = %self.chain_id(),
1705 block_height = %block.header.height
1706 ))]
1707 async fn process_outgoing_messages(
1708 &mut self,
1709 block: &Block,
1710 tracked: Option<&ChainIdSet>,
1711 ) -> Result<Vec<ChainId>, ChainError> {
1712 let recipients = block.recipients();
1715 let block_height = block.header.height;
1716 let next_height = self.tip_state.get().next_block_height;
1717
1718 let targets = recipients.into_iter().collect::<Vec<_>>();
1721 let outboxes = self.outboxes.try_load_entries_mut(&targets).await?;
1722 let mut scheduled_tracked = Vec::new();
1723 for (mut outbox, target) in outboxes.into_iter().zip(&targets) {
1724 if block_height > next_height {
1725 if *outbox.next_height_to_schedule.get() > block_height {
1728 continue; }
1730 let maybe_prev_hash = match outbox.next_height_to_schedule.get().try_sub_one().ok()
1731 {
1732 Some(height) => {
1733 Some(self.block_hashes.get(&height).await?.ok_or_else(|| {
1734 ChainError::CorruptedChainState("missing entry in block_hashes".into())
1735 })?)
1736 }
1737 None => None, };
1739 match (
1741 maybe_prev_hash,
1742 block.body.previous_message_blocks.get(target),
1743 ) {
1744 (None, None) => {
1745 }
1748 (Some(_), None) => {
1749 }
1757 (None, Some((_, prev_msg_block_height))) => {
1758 if *prev_msg_block_height >= next_height {
1763 continue;
1764 }
1765 }
1766 (Some(ref prev_hash), Some((prev_msg_block_hash, _))) => {
1767 if prev_hash != prev_msg_block_hash {
1773 continue;
1774 }
1775 }
1776 }
1777 }
1778 if outbox.schedule_message(block_height)?
1779 && tracked.is_none_or(|set| set.contains(target))
1780 {
1781 scheduled_tracked.push(*target);
1782 }
1783 #[cfg(with_metrics)]
1784 crate::outbox::metrics::OUTBOX_SIZE
1785 .with_label_values(&[])
1786 .observe(outbox.queue.count() as f64);
1787 }
1788
1789 if !scheduled_tracked.is_empty() {
1790 let scheduled_count =
1792 u32::try_from(scheduled_tracked.len()).map_err(|_| ArithmeticError::Overflow)?;
1793 *self
1794 .outbox_counters
1795 .get_mut()
1796 .entry(block_height)
1797 .or_default() += scheduled_count;
1798 let nonempty_outboxes = self.nonempty_outboxes.get_mut();
1799 for target in &scheduled_tracked {
1800 nonempty_outboxes.insert(*target);
1801 }
1802 }
1803
1804 #[cfg(with_metrics)]
1805 metrics::NUM_OUTBOXES
1806 .with_label_values(&[])
1807 .observe(self.nonempty_outboxes.get().len() as f64);
1808 #[cfg(with_metrics)]
1809 metrics::OUTBOX_COUNTERS_SIZE
1810 .with_label_values(&[])
1811 .observe(self.outbox_counters.get().len() as f64);
1812 Ok(targets)
1813 }
1814
1815 #[instrument(skip_all, fields(
1820 chain_id = %self.chain_id(),
1821 block_height = %block.header.height
1822 ))]
1823 async fn process_emitted_events(
1824 &mut self,
1825 block: &Block,
1826 ) -> Result<BTreeSet<StreamId>, ChainError> {
1827 let mut emitted_ranges = BTreeMap::<StreamId, (u32, u32)>::new();
1830 for event in block.body.events.iter().flatten() {
1831 emitted_ranges
1832 .entry(event.stream_id.clone())
1833 .and_modify(|(lo, hi)| {
1834 *lo = (*lo).min(event.index);
1835 *hi = (*hi).max(event.index);
1836 })
1837 .or_insert((event.index, event.index));
1838 }
1839 let mut stream_ids = Vec::new();
1840 let mut ranges = Vec::new();
1841 for (stream_id, range) in emitted_ranges {
1842 stream_ids.push(stream_id);
1843 ranges.push(range);
1844 }
1845
1846 let mut updated_streams = BTreeSet::new();
1847 for ((stream_id, counts), (lo, hi)) in self
1848 .next_expected_events
1849 .multi_get_pairs(stream_ids)
1850 .await?
1851 .into_iter()
1852 .zip(ranges)
1853 {
1854 let mut counts = counts.unwrap_or_default();
1855 let next = hi.saturating_add(1);
1856 if block.body.previous_event_blocks.contains_key(&stream_id) {
1857 if lo != counts.next_index {
1861 continue;
1862 }
1863 counts.next_index = next;
1864 } else if lo >= counts.first_index {
1865 counts.first_index = lo;
1870 counts.next_index = counts.next_index.max(next);
1871 } else {
1872 continue;
1874 }
1875 updated_streams.insert(stream_id.clone());
1876 self.next_expected_events.insert(&stream_id, counts)?;
1877 }
1878 Ok(updated_streams)
1879 }
1880}
1881
1882#[test]
1883fn empty_block_size() {
1884 let size = bcs::serialized_size(&crate::block::Block::new(
1885 crate::test::make_first_block(
1886 linera_execution::test_utils::dummy_chain_description(0).id(),
1887 ),
1888 crate::data_types::BlockExecutionOutcome::default(),
1889 ))
1890 .unwrap();
1891 assert_eq!(size, EMPTY_BLOCK_SIZE);
1892}