1use futures::{StreamExt, TryStreamExt};
22use mysten_common::{ZipDebugEqIteratorExt, debug_fatal, fatal, izip_debug_eq};
23use parking_lot::Mutex;
24use std::{sync::Arc, time::Instant};
25use sui_types::base_types::SequenceNumber;
26use sui_types::crypto::RandomnessRound;
27use sui_types::messages_checkpoint::{CheckpointContents, CheckpointSequenceNumber};
28use sui_types::transaction::{TransactionDataAPI, TransactionKind};
29use sui_types::{
30 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
31 node_role::{FullNodeSyncMode, NodeRole},
32};
33use tokio::task::JoinError;
34
35use sui_config::node::{CheckpointExecutorConfig, RunWithRange};
36use sui_macros::fail_point;
37use sui_types::effects::{TransactionEffects, TransactionEffectsAPI};
38use sui_types::executable_transaction::VerifiedExecutableTransaction;
39use sui_types::execution_status::{ExecutionErrorKind, ExecutionFailure, ExecutionStatus};
40use sui_types::full_checkpoint_content::Checkpoint;
41use sui_types::global_state_hash::GlobalStateHash;
42use sui_types::message_envelope::Message;
43use sui_types::{
44 base_types::{TransactionDigest, TransactionEffectsDigest},
45 messages_checkpoint::VerifiedCheckpoint,
46 transaction::VerifiedTransaction,
47};
48use tap::{TapFallible, TapOptional};
49use tracing::{debug, info, instrument};
50
51use crate::authority::authority_per_epoch_store::AuthorityPerEpochStore;
52use crate::authority::backpressure::BackpressureManager;
53use crate::authority::{AuthorityState, ExecutionEnv, ExpectedEffectsDigest};
54use crate::execution_scheduler::ExecutionScheduler;
55use crate::execution_scheduler::execution_scheduler_impl::BarrierDependencyBuilder;
56use crate::global_state_hasher::GlobalStateHasher;
57use crate::{
58 checkpoints::CheckpointStore,
59 execution_cache::{ObjectCacheRead, TransactionCacheRead},
60};
61
62mod data_ingestion_handler;
63pub mod metrics;
64pub(crate) mod utils;
65
66use data_ingestion_handler::{load_checkpoint, store_checkpoint_locally};
67use metrics::CheckpointExecutorMetrics;
68use utils::*;
69
70const CHECKPOINT_PROGRESS_LOG_COUNT_INTERVAL: u64 = 5000;
71
72#[derive(PartialEq, Eq, Debug)]
73pub enum StopReason {
74 EpochComplete,
75 RunWithRangeCondition,
76 RuntimeShutdown,
78}
79
80pub(crate) struct CheckpointExecutionData {
81 pub checkpoint: VerifiedCheckpoint,
82 pub checkpoint_contents: CheckpointContents,
83 pub tx_digests: Vec<TransactionDigest>,
84 pub fx_digests: Vec<TransactionEffectsDigest>,
85}
86
87pub(crate) struct CheckpointTransactionData {
88 pub transactions: Vec<VerifiedExecutableTransaction>,
89 pub effects: Vec<TransactionEffects>,
90 pub executed_fx_digests: Vec<Option<TransactionEffectsDigest>>,
91 pub accumulator_versions: Vec<Option<SequenceNumber>>,
96}
97
98impl CheckpointTransactionData {
99 pub fn new(
100 transactions: Vec<VerifiedExecutableTransaction>,
101 effects: Vec<TransactionEffects>,
102 executed_fx_digests: Vec<Option<TransactionEffectsDigest>>,
103 ) -> Self {
104 assert_eq!(transactions.len(), effects.len());
105 assert_eq!(transactions.len(), executed_fx_digests.len());
106 let mut accumulator_versions = vec![None; transactions.len()];
107 let mut next_update_index = 0;
108 for (idx, efx) in effects.iter().enumerate() {
109 let acc_version = efx.object_changes().into_iter().find_map(|change| {
114 if change.id == SUI_ACCUMULATOR_ROOT_OBJECT_ID {
115 change.input_version
116 } else {
117 None
118 }
119 });
120 if let Some(acc_version) = acc_version {
121 for slot in accumulator_versions
123 .iter_mut()
124 .take(idx + 1)
125 .skip(next_update_index)
126 {
127 *slot = Some(acc_version);
128 }
129 next_update_index = idx + 1;
130 }
131 }
132 assert!(
136 next_update_index == 0
137 || next_update_index == transactions.len()
138 || (next_update_index == transactions.len() - 1
139 && transactions
140 .last()
141 .unwrap()
142 .transaction_data()
143 .is_end_of_epoch_tx())
144 );
145 Self {
146 transactions,
147 effects,
148 executed_fx_digests,
149 accumulator_versions,
150 }
151 }
152}
153pub(crate) struct CheckpointExecutionState {
154 pub data: CheckpointExecutionData,
155
156 state_hasher: Option<GlobalStateHash>,
157 full_data: Option<Checkpoint>,
158}
159
160impl CheckpointExecutionState {
161 pub fn new(data: CheckpointExecutionData) -> Self {
162 Self {
163 data,
164 state_hasher: None,
165 full_data: None,
166 }
167 }
168
169 pub fn new_with_global_state_hasher(
170 data: CheckpointExecutionData,
171 hasher: GlobalStateHash,
172 ) -> Self {
173 Self {
174 data,
175 state_hasher: Some(hasher),
176 full_data: None,
177 }
178 }
179}
180
181macro_rules! finish_stage {
182 ($handle:expr, $stage:ident) => {
183 $handle.finish_stage(PipelineStage::$stage).await;
184 };
185}
186
187pub struct CheckpointExecutor {
188 epoch_store: Arc<AuthorityPerEpochStore>,
189 state: Arc<AuthorityState>,
190 checkpoint_store: Arc<CheckpointStore>,
193 object_cache_reader: Arc<dyn ObjectCacheRead>,
194 transaction_cache_reader: Arc<dyn TransactionCacheRead>,
195 execution_scheduler: Arc<ExecutionScheduler>,
196 global_state_hasher: Arc<GlobalStateHasher>,
197 backpressure_manager: Arc<BackpressureManager>,
198 config: CheckpointExecutorConfig,
199 metrics: Arc<CheckpointExecutorMetrics>,
200 tps_estimator: Mutex<TPSEstimator>,
201 subscription_service_checkpoint_sender: Option<tokio::sync::broadcast::Sender<Arc<Checkpoint>>>,
202}
203
204impl CheckpointExecutor {
205 pub fn new(
206 epoch_store: Arc<AuthorityPerEpochStore>,
207 checkpoint_store: Arc<CheckpointStore>,
208 state: Arc<AuthorityState>,
209 global_state_hasher: Arc<GlobalStateHasher>,
210 backpressure_manager: Arc<BackpressureManager>,
211 config: CheckpointExecutorConfig,
212 metrics: Arc<CheckpointExecutorMetrics>,
213 subscription_service_checkpoint_sender: Option<
214 tokio::sync::broadcast::Sender<Arc<Checkpoint>>,
215 >,
216 ) -> Self {
217 Self {
218 epoch_store,
219 state: state.clone(),
220 checkpoint_store,
221 object_cache_reader: state.get_object_cache_reader().clone(),
222 transaction_cache_reader: state.get_transaction_cache_reader().clone(),
223 execution_scheduler: state.execution_scheduler().clone(),
224 global_state_hasher,
225 backpressure_manager,
226 config,
227 metrics,
228 tps_estimator: Mutex::new(TPSEstimator::default()),
229 subscription_service_checkpoint_sender,
230 }
231 }
232
233 pub fn new_for_tests(
234 epoch_store: Arc<AuthorityPerEpochStore>,
235 checkpoint_store: Arc<CheckpointStore>,
236 state: Arc<AuthorityState>,
237 state_hasher: Arc<GlobalStateHasher>,
238 ) -> Self {
239 Self::new(
240 epoch_store,
241 checkpoint_store,
242 state,
243 state_hasher,
244 BackpressureManager::new_for_tests(),
245 Default::default(),
246 CheckpointExecutorMetrics::new_for_tests(),
247 None,
248 )
249 }
250
251 fn get_next_to_schedule(&self) -> Option<CheckpointSequenceNumber> {
254 let highest_executed = self
258 .checkpoint_store
259 .get_highest_executed_checkpoint()
260 .unwrap();
261
262 if let Some(highest_executed) = &highest_executed
263 && self.epoch_store.epoch() == highest_executed.epoch()
264 && highest_executed.is_last_checkpoint_of_epoch()
265 {
266 info!(seq = ?highest_executed.sequence_number, "final checkpoint of epoch has already been executed");
269 return None;
270 }
271
272 Some(
273 highest_executed
274 .as_ref()
275 .map(|c| c.sequence_number() + 1)
276 .unwrap_or_else(|| {
277 assert_eq!(self.epoch_store.epoch(), 0);
279 0
281 }),
282 )
283 }
284
285 #[instrument(level = "error", skip_all, fields(epoch = ?self.epoch_store.epoch()))]
289 pub async fn run_epoch(self, run_with_range: Option<RunWithRange>) -> StopReason {
290 let _metrics_scope = mysten_metrics::monitored_scope("CheckpointExecutor::run_epoch");
291 info!(?run_with_range, "CheckpointExecutor::run_epoch");
292 debug!(
293 "Checkpoint executor running for epoch {:?}",
294 self.epoch_store.epoch(),
295 );
296
297 if run_with_range.is_some_and(|rwr| rwr.is_epoch_gt(self.epoch_store.epoch())) {
301 info!("RunWithRange condition satisfied at {:?}", run_with_range,);
302 return StopReason::RunWithRangeCondition;
303 };
304
305 self.metrics
306 .checkpoint_exec_epoch
307 .set(self.epoch_store.epoch() as i64);
308
309 let Some(next_to_schedule) = self.get_next_to_schedule() else {
310 return StopReason::EpochComplete;
311 };
312
313 let this = Arc::new(self);
314
315 let concurrency = std::env::var("SUI_CHECKPOINT_EXECUTION_MAX_CONCURRENCY")
316 .ok()
317 .and_then(|s| s.parse().ok())
318 .unwrap_or(this.config.checkpoint_execution_max_concurrency);
319
320 let pipeline_stages = PipelineStages::new(next_to_schedule, this.metrics.clone());
321
322 let final_checkpoint_executed = stream_synced_checkpoints(
323 this.checkpoint_store.clone(),
324 next_to_schedule,
325 run_with_range.and_then(|rwr| rwr.into_checkpoint_bound()),
326 )
327 .map(|checkpoint| {
329 let this = this.clone();
330 let pipeline_handle = pipeline_stages.handle(*checkpoint.sequence_number());
331 async move {
332 let pipeline_handle = pipeline_handle.await;
333 tokio::spawn(this.execute_checkpoint(checkpoint, pipeline_handle))
334 .await
335 .and_then(|result| result)
336 }
337 })
338 .buffered(concurrency)
339 .try_fold(false, |state, is_final_checkpoint| async move {
341 assert!(
342 !state,
343 "fold can't be called again after the final checkpoint"
344 );
345 Ok(is_final_checkpoint)
346 })
347 .await;
348
349 match final_checkpoint_executed {
350 Ok(true) => StopReason::EpochComplete,
351 Ok(false) => StopReason::RunWithRangeCondition,
352 Err(e) if e.is_panic() => std::panic::resume_unwind(e.into_panic()),
353 Err(e) => {
355 info!("Checkpoint execution cancelled by runtime shutdown: {e}");
356 StopReason::RuntimeShutdown
357 }
358 }
359 }
360}
361
362impl CheckpointExecutor {
363 #[instrument(level = "info", skip_all, fields(seq = ?checkpoint.sequence_number()))]
365 async fn execute_checkpoint(
366 self: Arc<Self>,
367 checkpoint: VerifiedCheckpoint,
368 mut pipeline_handle: PipelineHandle,
369 ) -> Result<bool , JoinError> {
370 info!("executing checkpoint");
371 let sequence_number = checkpoint.sequence_number;
372
373 checkpoint.report_checkpoint_age(
374 &self.metrics.checkpoint_contents_age,
375 &self.metrics.checkpoint_contents_age_ms,
376 );
377 self.backpressure_manager
378 .update_highest_certified_checkpoint(sequence_number);
379
380 if checkpoint.is_last_checkpoint_of_epoch() && sequence_number > 0 {
381 let _wait_for_previous_checkpoints_guard = mysten_metrics::monitored_scope(
382 "CheckpointExecutor::wait_for_previous_checkpoints",
383 );
384
385 info!(
386 "Reached end of epoch checkpoint, waiting for all previous checkpoints to be executed"
387 );
388 self.checkpoint_store
389 .notify_read_executed_checkpoint(sequence_number - 1)
390 .await;
391 }
392
393 let _parallel_step_guard =
394 mysten_metrics::monitored_scope("CheckpointExecutor::parallel_step");
395
396 let ckpt_state = if !self.epoch_store.node_role().runs_consensus()
401 || checkpoint.is_last_checkpoint_of_epoch()
402 {
403 self.execute_transactions_from_synced_checkpoint(checkpoint, &mut pipeline_handle)
404 .await
405 } else {
406 self.verify_locally_built_checkpoint(checkpoint, &mut pipeline_handle)
407 .await
408 };
409
410 let tps = self.tps_estimator.lock().update(
411 Instant::now(),
412 ckpt_state.data.checkpoint.network_total_transactions,
413 );
414 self.metrics.checkpoint_exec_sync_tps.set(tps as i64);
415
416 self.backpressure_manager
417 .update_highest_executed_checkpoint(*ckpt_state.data.checkpoint.sequence_number());
418
419 let is_final_checkpoint = ckpt_state.data.checkpoint.is_last_checkpoint_of_epoch();
420
421 let seq = ckpt_state.data.checkpoint.sequence_number;
422
423 let mut batch = self
424 .state
425 .get_cache_commit()
426 .build_db_batch(self.epoch_store.epoch(), &ckpt_state.data.tx_digests);
427
428 self.state
434 .get_cache_commit()
435 .set_highest_committed_checkpoint_in_batch(&mut batch, seq);
436
437 finish_stage!(pipeline_handle, BuildDbBatch);
438
439 let committed_accumulator_versions = batch
443 .0
444 .iter()
445 .filter_map(|outputs| {
446 outputs.effects.object_changes().into_iter().find_map(|o| {
447 if o.id == SUI_ACCUMULATOR_ROOT_OBJECT_ID {
448 o.input_version
449 } else {
450 None
451 }
452 })
453 })
454 .collect::<Vec<_>>();
455 self.state
456 .unsettled_object_withdrawals
457 .commit_accumulator_versions(committed_accumulator_versions);
458
459 let mut ckpt_state = tokio::task::spawn_blocking({
460 let this = self.clone();
461 move || {
462 let cache_commit = this.state.get_cache_commit();
464 debug!(?seq, "committing checkpoint transactions to disk");
465 cache_commit.commit_transaction_outputs(
466 this.epoch_store.epoch(),
467 batch,
468 &ckpt_state.data.tx_digests,
469 );
470 ckpt_state
471 }
472 })
473 .await?;
474
475 finish_stage!(pipeline_handle, CommitTransactionOutputs);
476
477 self.epoch_store
478 .handle_finalized_checkpoint(&ckpt_state.data.checkpoint, &ckpt_state.data.tx_digests)
479 .expect("cannot fail");
480
481 let randomness_rounds = self.extract_randomness_rounds(
482 &ckpt_state.data.checkpoint,
483 &ckpt_state.data.checkpoint_contents,
484 );
485
486 if let Some(randomness_reporter) = self.epoch_store.randomness_reporter() {
490 for round in randomness_rounds {
491 debug!(
492 ?round,
493 "notifying RandomnessReporter that randomness update was executed in checkpoint"
494 );
495 randomness_reporter
496 .notify_randomness_in_checkpoint(round)
497 .expect("epoch cannot have ended");
498 }
499 }
500
501 finish_stage!(pipeline_handle, FinalizeCheckpoint);
502
503 if let Some(checkpoint_data) = ckpt_state.full_data.take() {
504 self.enqueue_to_subscription_service(checkpoint_data);
505 }
506
507 finish_stage!(pipeline_handle, UpdateRpcIndex);
508
509 self.global_state_hasher
510 .accumulate_running_root(&self.epoch_store, seq, ckpt_state.state_hasher)
511 .expect("Failed to accumulate running root");
512
513 if is_final_checkpoint {
514 self.checkpoint_store
515 .insert_epoch_last_checkpoint(self.epoch_store.epoch(), &ckpt_state.data.checkpoint)
516 .expect("Failed to insert epoch last checkpoint");
517
518 self.global_state_hasher
519 .accumulate_epoch(self.epoch_store.clone(), seq)
520 .expect("Accumulating epoch cannot fail");
521
522 self.checkpoint_store
523 .prune_local_summaries()
524 .tap_err(|e| debug_fatal!("Failed to prune local summaries: {}", e))
525 .ok();
526 }
527
528 fail_point!("crash");
529
530 self.bump_highest_executed_checkpoint(&ckpt_state.data.checkpoint);
531
532 finish_stage!(pipeline_handle, BumpHighestExecutedCheckpoint);
533
534 Ok(ckpt_state.data.checkpoint.is_last_checkpoint_of_epoch())
537 }
538
539 #[instrument(level = "info", skip_all)]
545 async fn verify_locally_built_checkpoint(
546 &self,
547 checkpoint: VerifiedCheckpoint,
548 pipeline_handle: &mut PipelineHandle,
549 ) -> CheckpointExecutionState {
550 assert!(
551 !checkpoint.is_last_checkpoint_of_epoch(),
552 "only fullnode path has end-of-epoch logic"
553 );
554
555 let sequence_number = checkpoint.sequence_number;
556 let locally_built_checkpoint = self
557 .checkpoint_store
558 .get_locally_computed_checkpoint(sequence_number)
559 .expect("db error");
560
561 let Some(locally_built_checkpoint) = locally_built_checkpoint else {
562 self.metrics
564 .checkpoint_executor_validator_sync_fallback_path
565 .inc();
566 return self
567 .execute_transactions_from_synced_checkpoint(checkpoint, pipeline_handle)
568 .await;
569 };
570
571 self.metrics.checkpoint_executor_validator_path.inc();
572
573 assert_checkpoint_not_forked(
575 &locally_built_checkpoint,
576 &checkpoint,
577 &self.checkpoint_store,
578 );
579
580 let state_hasher = {
582 let _metrics_scope =
583 mysten_metrics::monitored_scope("CheckpointExecutor::notify_read_state_hasher");
584 self.epoch_store
585 .notify_read_checkpoint_state_hasher(&[sequence_number])
586 .await
587 .pop()
588 .unwrap()
589 };
590
591 if matches!(
594 self.epoch_store.node_role(),
595 NodeRole::FullNode(FullNodeSyncMode::ConsensusObserver)
596 ) {
597 pipeline_handle
598 .skip_to(PipelineStage::FinalizeTransactions)
599 .await;
600
601 let (state, tx_data) =
602 self.load_checkpoint_transactions(checkpoint, Some(state_hasher));
603
604 return self
605 .finalize_executed_checkpoint_transactions(state, &tx_data, pipeline_handle)
606 .await;
607 }
608
609 let checkpoint_contents = self
610 .checkpoint_store
611 .get_checkpoint_contents(&checkpoint.content_digest)
612 .expect("db error")
613 .expect("checkpoint contents not found");
614
615 let (tx_digests, fx_digests): (Vec<_>, Vec<_>) = checkpoint_contents
616 .iter()
617 .map(|digests| (digests.transaction, digests.effects))
618 .unzip();
619
620 pipeline_handle
621 .skip_to(PipelineStage::FinalizeTransactions)
622 .await;
623
624 self.insert_finalized_transactions(&tx_digests, sequence_number);
625
626 pipeline_handle.skip_to(PipelineStage::BuildDbBatch).await;
627
628 let ckpt_data = CheckpointExecutionData {
629 checkpoint,
630 checkpoint_contents,
631 tx_digests,
632 fx_digests,
633 };
634 CheckpointExecutionState::new_with_global_state_hasher(ckpt_data, state_hasher)
635 }
636
637 #[instrument(level = "info", skip_all)]
638 async fn execute_transactions_from_synced_checkpoint(
639 &self,
640 checkpoint: VerifiedCheckpoint,
641 pipeline_handle: &mut PipelineHandle,
642 ) -> CheckpointExecutionState {
643 let (ckpt_state, tx_data, unexecuted_tx_digests) = {
644 let _scope =
645 mysten_metrics::monitored_scope("CheckpointExecutor::execute_transactions");
646 let (ckpt_state, tx_data) = self.load_checkpoint_transactions(checkpoint, None);
647 let unexecuted_tx_digests = self.schedule_transaction_execution(&ckpt_state, &tx_data);
648 (ckpt_state, tx_data, unexecuted_tx_digests)
649 };
650
651 finish_stage!(pipeline_handle, ExecuteTransactions);
652
653 {
654 self.transaction_cache_reader
655 .notify_read_executed_effects_digests(
656 "CheckpointExecutor::notify_read_executed_effects_digests",
657 &unexecuted_tx_digests,
658 )
659 .await;
660 }
661
662 finish_stage!(pipeline_handle, WaitForTransactions);
663
664 if ckpt_state.data.checkpoint.is_last_checkpoint_of_epoch() {
665 self.execute_change_epoch_tx(&tx_data, ckpt_state.data.checkpoint.sequence_number)
666 .await;
667 }
668
669 self.finalize_executed_checkpoint_transactions(ckpt_state, &tx_data, pipeline_handle)
670 .await
671 }
672
673 async fn finalize_executed_checkpoint_transactions(
674 &self,
675 mut ckpt_state: CheckpointExecutionState,
676 tx_data: &CheckpointTransactionData,
677 pipeline_handle: &mut PipelineHandle,
678 ) -> CheckpointExecutionState {
679 let sequence_number = ckpt_state.data.checkpoint.sequence_number;
680
681 let _scope = mysten_metrics::monitored_scope("CheckpointExecutor::finalize_checkpoint");
682
683 if self.state.is_fullnode(&self.epoch_store) {
684 self.state.congestion_tracker.process_checkpoint_effects(
685 &*self.transaction_cache_reader,
686 &ckpt_state.data.checkpoint,
687 &tx_data.effects,
688 );
689 }
690
691 self.insert_finalized_transactions(&ckpt_state.data.tx_digests, sequence_number);
692
693 if ckpt_state.state_hasher.is_none() {
697 ckpt_state.state_hasher = Some(self.global_state_hasher.accumulate_checkpoint(
698 &tx_data.effects,
699 sequence_number,
700 &self.epoch_store,
701 ));
702 }
703
704 finish_stage!(pipeline_handle, FinalizeTransactions);
705
706 ckpt_state.full_data = self.process_checkpoint_data(&ckpt_state.data, tx_data);
707
708 finish_stage!(pipeline_handle, ProcessCheckpointData);
709
710 ckpt_state
711 }
712
713 fn checkpoint_data_enabled(&self) -> bool {
714 self.subscription_service_checkpoint_sender.is_some()
715 || self.config.data_ingestion_dir.is_some()
716 }
717
718 fn insert_finalized_transactions(
719 &self,
720 tx_digests: &[TransactionDigest],
721 sequence_number: CheckpointSequenceNumber,
722 ) {
723 self.epoch_store
724 .insert_finalized_transactions(tx_digests, sequence_number)
725 .expect("failed to insert finalized transactions");
726
727 if self.state.is_fullnode(&self.epoch_store) {
728 self.state
730 .get_checkpoint_cache()
731 .deprecated_insert_finalized_transactions(
732 tx_digests,
733 self.epoch_store.epoch(),
734 sequence_number,
735 );
736 }
737 }
738
739 #[instrument(level = "info", skip_all)]
740 fn process_checkpoint_data(
741 &self,
742 ckpt_data: &CheckpointExecutionData,
743 tx_data: &CheckpointTransactionData,
744 ) -> Option<Checkpoint> {
745 if !self.checkpoint_data_enabled() {
746 return None;
747 }
748
749 let checkpoint = load_checkpoint(
750 ckpt_data,
751 tx_data,
752 self.state.get_object_store(),
753 &*self.transaction_cache_reader,
754 )
755 .expect("failed to load checkpoint data");
756
757 if let Some(path) = &self.config.data_ingestion_dir {
758 store_checkpoint_locally(path, &checkpoint)
759 .expect("failed to store checkpoint locally");
760 }
761
762 Some(checkpoint)
763 }
764
765 #[instrument(level = "info", skip_all)]
769 fn load_checkpoint_transactions(
770 &self,
771 checkpoint: VerifiedCheckpoint,
772 state_hasher: Option<GlobalStateHash>,
773 ) -> (CheckpointExecutionState, CheckpointTransactionData) {
774 let seq = checkpoint.sequence_number;
775 let epoch = checkpoint.epoch;
776
777 let checkpoint_contents = self
778 .checkpoint_store
779 .get_checkpoint_contents(&checkpoint.content_digest)
780 .expect("db error")
781 .expect("checkpoint contents not found");
782
783 let checkpoint_state = |data| match state_hasher {
784 Some(hasher) => CheckpointExecutionState::new_with_global_state_hasher(data, hasher),
785 None => CheckpointExecutionState::new(data),
786 };
787
788 if let Some(full_contents) = self
792 .checkpoint_store
793 .get_full_checkpoint_contents_by_sequence_number(seq)
794 .tap_err(|e| debug_fatal!("Failed to get checkpoint contents from store: {e}"))
795 .ok()
796 .flatten()
797 .tap_some(|_| debug!("loaded full checkpoint contents in bulk for sequence {seq}"))
798 {
799 let num_txns = full_contents.size();
800 let mut tx_digests = Vec::with_capacity(num_txns);
801 let mut transactions = Vec::with_capacity(num_txns);
802 let mut effects = Vec::with_capacity(num_txns);
803 let mut fx_digests = Vec::with_capacity(num_txns);
804
805 full_contents
806 .into_iter()
807 .zip_debug_eq(checkpoint_contents.iter())
808 .for_each(|(execution_data, digests)| {
809 let tx_digest = digests.transaction;
810 let fx_digest = digests.effects;
811 debug_assert_eq!(tx_digest, *execution_data.transaction.digest());
812 debug_assert_eq!(fx_digest, execution_data.effects.digest());
813
814 tx_digests.push(tx_digest);
815 transactions.push(VerifiedExecutableTransaction::new_from_checkpoint(
816 VerifiedTransaction::new_unchecked(execution_data.transaction),
817 epoch,
818 seq,
819 ));
820 effects.push(execution_data.effects);
821 fx_digests.push(fx_digest);
822 });
823
824 let executed_fx_digests = self
825 .transaction_cache_reader
826 .multi_get_executed_effects_digests(&tx_digests);
827
828 (
829 checkpoint_state(CheckpointExecutionData {
830 checkpoint,
831 checkpoint_contents,
832 tx_digests,
833 fx_digests,
834 }),
835 CheckpointTransactionData::new(transactions, effects, executed_fx_digests),
836 )
837 } else {
838 let digests = checkpoint_contents.inner();
843
844 let (tx_digests, fx_digests): (Vec<_>, Vec<_>) = digests
845 .digests_iter()
846 .map(|d| (d.transaction, d.effects))
847 .unzip();
848 let transactions = self
849 .transaction_cache_reader
850 .multi_get_transaction_blocks(&tx_digests)
851 .into_iter()
852 .enumerate()
853 .map(|(i, tx)| {
854 let tx = tx
855 .unwrap_or_else(|| fatal!("transaction not found for {:?}", tx_digests[i]));
856 let tx = Arc::try_unwrap(tx).unwrap_or_else(|tx| (*tx).clone());
857 VerifiedExecutableTransaction::new_from_checkpoint(tx, epoch, seq)
858 })
859 .collect();
860 let effects = self
861 .transaction_cache_reader
862 .multi_get_effects(&fx_digests)
863 .into_iter()
864 .enumerate()
865 .map(|(i, effect)| {
866 effect.unwrap_or_else(|| {
867 fatal!("checkpoint effect not found for {:?}", digests[i])
868 })
869 })
870 .collect();
871
872 let executed_fx_digests = self
873 .transaction_cache_reader
874 .multi_get_executed_effects_digests(&tx_digests);
875
876 (
877 checkpoint_state(CheckpointExecutionData {
878 checkpoint,
879 checkpoint_contents,
880 tx_digests,
881 fx_digests,
882 }),
883 CheckpointTransactionData::new(transactions, effects, executed_fx_digests),
884 )
885 }
886 }
887
888 #[instrument(level = "info", skip_all)]
890 fn schedule_transaction_execution(
891 &self,
892 ckpt_state: &CheckpointExecutionState,
893 tx_data: &CheckpointTransactionData,
894 ) -> Vec<TransactionDigest> {
895 let mut barrier_deps_builder = BarrierDependencyBuilder::new();
896
897 let (unexecuted_tx_digests, unexecuted_txns): (Vec<_>, Vec<_>) = itertools::multiunzip(
899 izip_debug_eq!(
900 tx_data.transactions.iter(),
901 ckpt_state.data.tx_digests.iter(),
902 ckpt_state.data.fx_digests.iter(),
903 tx_data.effects.iter(),
904 tx_data.executed_fx_digests.iter(),
905 tx_data.accumulator_versions.iter()
906 )
907 .filter_map(
908 |(
909 txn,
910 tx_digest,
911 expected_fx_digest,
912 effects,
913 executed_fx_digest,
914 accumulator_version,
915 )| {
916 let barrier_deps =
917 barrier_deps_builder.process_tx(*tx_digest, txn.transaction_data());
918
919 if let Some(executed_fx_digest) = executed_fx_digest {
920 assert_not_forked(
921 &ckpt_state.data.checkpoint,
922 tx_digest,
923 expected_fx_digest,
924 executed_fx_digest,
925 &*self.transaction_cache_reader,
926 );
927 None
928 } else if txn.transaction_data().is_end_of_epoch_tx() {
929 None
930 } else {
931 let assigned_versions = self
932 .epoch_store
933 .acquire_shared_version_assignments_from_effects(
934 txn,
935 effects,
936 *accumulator_version,
937 &*self.object_cache_reader,
938 );
939
940 let mut env = ExecutionEnv::new()
941 .with_assigned_versions(assigned_versions)
942 .with_expected_effects(ExpectedEffectsDigest::Certified {
943 digest: *expected_fx_digest,
944 checkpoint_seq: ckpt_state.data.checkpoint.sequence_number,
945 })
946 .with_barrier_dependencies(barrier_deps);
947
948 if let &ExecutionStatus::Failure(ExecutionFailure {
950 error: ExecutionErrorKind::InsufficientFundsForWithdraw,
951 ..
952 }) = effects.status()
953 {
954 env = env.with_insufficient_funds();
955 }
956
957 Some((tx_digest, (txn.clone(), env)))
958 }
959 },
960 ),
961 );
962
963 for (txn, _) in &unexecuted_txns {
967 if let Some(key) = txn.non_digest_key() {
968 self.epoch_store.insert_tx_key(key, *txn.digest());
969 }
970 }
971
972 self.execution_scheduler
974 .enqueue_transactions(unexecuted_txns, &self.epoch_store);
975
976 unexecuted_tx_digests
977 }
978
979 #[instrument(level = "error", skip_all)]
981 async fn execute_change_epoch_tx(
982 &self,
983 tx_data: &CheckpointTransactionData,
984 checkpoint_seq: CheckpointSequenceNumber,
985 ) {
986 let change_epoch_tx = tx_data.transactions.last().unwrap();
987 let change_epoch_fx = tx_data.effects.last().unwrap();
988 assert_eq!(
989 change_epoch_tx.digest(),
990 change_epoch_fx.transaction_digest()
991 );
992 assert!(
993 change_epoch_tx.transaction_data().is_end_of_epoch_tx(),
994 "final txn must be an end of epoch txn"
995 );
996
997 let assigned_versions = self
1015 .epoch_store
1016 .acquire_shared_version_assignments_from_effects(
1017 change_epoch_tx,
1018 change_epoch_fx,
1019 None,
1020 self.object_cache_reader.as_ref(),
1021 );
1022
1023 info!(
1024 "scheduling change epoch txn with digest: {:?}, expected effects digest: {:?}, assigned versions: {:?}",
1025 change_epoch_tx.digest(),
1026 change_epoch_fx.digest(),
1027 assigned_versions
1028 );
1029 self.execution_scheduler.enqueue_transactions(
1030 vec![(
1031 change_epoch_tx.clone(),
1032 ExecutionEnv::new()
1033 .with_assigned_versions(assigned_versions)
1034 .with_expected_effects(ExpectedEffectsDigest::Certified {
1035 digest: change_epoch_fx.digest(),
1036 checkpoint_seq,
1037 }),
1038 )],
1039 &self.epoch_store,
1040 );
1041
1042 self.transaction_cache_reader
1043 .notify_read_executed_effects_digests(
1044 "CheckpointExecutor::notify_read_advance_epoch_tx",
1045 &[*change_epoch_tx.digest()],
1046 )
1047 .await;
1048 }
1049
1050 #[instrument(level = "debug", skip_all)]
1052 fn bump_highest_executed_checkpoint(&self, checkpoint: &VerifiedCheckpoint) {
1053 let seq = *checkpoint.sequence_number();
1055 debug!("Bumping highest_executed_checkpoint watermark to {seq:?}");
1056 if let Some(prev_highest) = self
1057 .checkpoint_store
1058 .get_highest_executed_checkpoint_seq_number()
1059 .unwrap()
1060 {
1061 assert_eq!(prev_highest + 1, seq);
1062 } else {
1063 assert_eq!(seq, 0);
1064 }
1065 if seq.is_multiple_of(CHECKPOINT_PROGRESS_LOG_COUNT_INTERVAL) {
1066 info!("Finished syncing and executing checkpoint {}", seq);
1067 }
1068
1069 fail_point!("highest-executed-checkpoint");
1070
1071 const NUM_SAVED_FULL_CHECKPOINT_CONTENTS: u64 = 5_000;
1074 if seq >= NUM_SAVED_FULL_CHECKPOINT_CONTENTS {
1075 let prune_seq = seq - NUM_SAVED_FULL_CHECKPOINT_CONTENTS;
1076 if let Some(prune_checkpoint) = self
1077 .checkpoint_store
1078 .get_checkpoint_by_sequence_number(prune_seq)
1079 .expect("Failed to fetch checkpoint")
1080 {
1081 self.checkpoint_store
1082 .delete_full_checkpoint_contents(prune_seq)
1083 .expect("Failed to delete full checkpoint contents");
1084 self.checkpoint_store
1085 .delete_contents_digest_sequence_number_mapping(
1086 &prune_checkpoint.content_digest,
1087 )
1088 .expect("Failed to delete contents digest -> sequence number mapping");
1089 } else {
1090 debug!(
1094 "Failed to fetch checkpoint with sequence number {:?}",
1095 prune_seq
1096 );
1097 }
1098 }
1099
1100 self.checkpoint_store
1101 .update_highest_executed_checkpoint(checkpoint)
1102 .unwrap();
1103 self.metrics.last_executed_checkpoint.set(seq as i64);
1104
1105 self.metrics
1106 .last_executed_checkpoint_timestamp_ms
1107 .set(checkpoint.timestamp_ms as i64);
1108 checkpoint.report_checkpoint_age(
1109 &self.metrics.last_executed_checkpoint_age,
1110 &self.metrics.last_executed_checkpoint_age_ms,
1111 );
1112 }
1113
1114 #[instrument(level = "info", skip_all)]
1118 fn enqueue_to_subscription_service(&self, checkpoint: Checkpoint) {
1119 if let Some(sender) = &self.subscription_service_checkpoint_sender {
1125 let _ = sender.send(Arc::new(checkpoint));
1126 }
1127 }
1128
1129 #[instrument(level = "debug", skip_all)]
1132 fn extract_randomness_rounds(
1133 &self,
1134 checkpoint: &VerifiedCheckpoint,
1135 checkpoint_contents: &CheckpointContents,
1136 ) -> Vec<RandomnessRound> {
1137 if let Some(version_specific_data) = checkpoint
1138 .version_specific_data(self.epoch_store.protocol_config())
1139 .expect("unable to get version_specific_data")
1140 {
1141 version_specific_data.into_v1().randomness_rounds
1143 } else {
1144 assert_eq!(
1149 0,
1150 self.epoch_store
1151 .protocol_config()
1152 .min_checkpoint_interval_ms_as_option()
1153 .unwrap_or_default(),
1154 );
1155 if let Some(first_digest) = checkpoint_contents.inner().first_digests() {
1156 let maybe_randomness_tx = self.transaction_cache_reader.get_transaction_block(&first_digest.transaction)
1157 .unwrap_or_else(||
1158 fatal!(
1159 "state-sync should have ensured that transaction with digests {first_digest:?} exists for checkpoint: {}",
1160 checkpoint.sequence_number()
1161 )
1162 );
1163 if let TransactionKind::RandomnessStateUpdate(rsu) =
1164 maybe_randomness_tx.data().transaction_data().kind()
1165 {
1166 vec![rsu.randomness_round]
1167 } else {
1168 Vec::new()
1169 }
1170 } else {
1171 Vec::new()
1172 }
1173 }
1174 }
1175}