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