1use std::{sync::Arc, time::Instant};
25
26use futures::StreamExt;
27use iota_common::{debug_fatal, fatal};
28use iota_config::node::{CheckpointExecutorConfig, RunWithRange};
29use iota_macros::fail_point;
30use iota_sdk_types::{
31 RandomnessRound, TransactionDigest, TransactionEffects, TransactionEffectsDigest,
32 TransactionKind, checkpoint::CheckpointContents,
33};
34use iota_types::{
35 base_types::ExecutionData,
36 effects::TransactionEffectsAPI,
37 executable_transaction::VerifiedExecutableTransaction,
38 full_checkpoint_content::CheckpointData,
39 global_state_hash::GlobalStateHash,
40 messages_checkpoint::{
41 CheckpointContentsExt, CheckpointSequenceNumber, CheckpointSummaryExt,
42 FullCheckpointContents, VerifiedCheckpoint,
43 },
44 transaction::{
45 SenderSignedTransactionAPI, TransactionDataAPI, TransactionKey, VerifiedTransaction,
46 },
47};
48use parking_lot::Mutex;
49use tap::{TapFallible, TapOptional};
50use tracing::{debug, info, instrument};
51
52use crate::{
53 authority::{
54 AuthorityState, authority_per_epoch_store::AuthorityPerEpochStore,
55 backpressure::BackpressureManager,
56 },
57 checkpoint_progress_tracker::CheckpointProgressTracker,
58 checkpoints::CheckpointStore,
59 execution_cache::{ObjectCacheRead, TransactionCacheRead},
60 global_state_hasher::GlobalStateHasher,
61 transaction_manager::TransactionManager,
62};
63
64mod data_ingestion_handler;
65pub mod metrics;
66pub(crate) mod utils;
67
68#[cfg(test)]
69pub(crate) mod tests;
70
71use data_ingestion_handler::{load_checkpoint_data, store_checkpoint_locally};
72use metrics::CheckpointExecutorMetrics;
73use utils::*;
74
75type CheckpointDataSender = Box<dyn Fn(&CheckpointData) + Send + Sync>;
76
77#[derive(PartialEq, Eq, Debug)]
78pub enum StopReason {
79 EpochComplete,
80 RunWithRangeCondition,
81}
82
83pub(crate) struct CheckpointExecutionData {
84 pub checkpoint: VerifiedCheckpoint,
85 pub checkpoint_contents: CheckpointContents,
86 pub tx_digests: Vec<TransactionDigest>,
87 pub fx_digests: Vec<TransactionEffectsDigest>,
88}
89
90pub(crate) struct CheckpointTransactionData {
91 pub transactions: Vec<VerifiedExecutableTransaction>,
92 pub effects: Vec<TransactionEffects>,
93 pub executed_fx_digests: Vec<Option<TransactionEffectsDigest>>,
94}
95
96pub(crate) struct CheckpointExecutionState {
97 pub data: CheckpointExecutionData,
98 state_hash: Option<GlobalStateHash>,
99 full_data: Option<CheckpointData>,
100}
101
102impl CheckpointExecutionState {
103 pub fn new(data: CheckpointExecutionData) -> Self {
104 Self {
105 data,
106 state_hash: None,
107 full_data: None,
108 }
109 }
110
111 pub fn new_with_global_state_hash(
112 data: CheckpointExecutionData,
113 hash: GlobalStateHash,
114 ) -> Self {
115 Self {
116 data,
117 state_hash: Some(hash),
118 full_data: None,
119 }
120 }
121}
122
123macro_rules! finish_stage {
124 ($handle:expr, $stage:ident) => {
125 $handle.finish_stage(PipelineStage::$stage).await;
126 };
127}
128
129pub struct CheckpointExecutor {
130 epoch_store: Arc<AuthorityPerEpochStore>,
131 state: Arc<AuthorityState>,
132 checkpoint_store: Arc<CheckpointStore>,
133 object_cache_reader: Arc<dyn ObjectCacheRead>,
134 transaction_cache_reader: Arc<dyn TransactionCacheRead>,
135 tx_manager: Arc<TransactionManager>,
136 global_state_hasher: Arc<GlobalStateHasher>,
137 backpressure_manager: Arc<BackpressureManager>,
138 config: CheckpointExecutorConfig,
139 metrics: Arc<CheckpointExecutorMetrics>,
140 tps_estimator: Mutex<TPSEstimator>,
141 checkpoint_progress_tracker: Option<Arc<CheckpointProgressTracker>>,
142 data_sender: Option<CheckpointDataSender>,
143}
144
145impl CheckpointExecutor {
146 pub fn new(
147 epoch_store: Arc<AuthorityPerEpochStore>,
148 checkpoint_store: Arc<CheckpointStore>,
149 state: Arc<AuthorityState>,
150 global_state_hasher: Arc<GlobalStateHasher>,
151 backpressure_manager: Arc<BackpressureManager>,
152 config: CheckpointExecutorConfig,
153 metrics: Arc<CheckpointExecutorMetrics>,
154 data_sender: Option<CheckpointDataSender>,
155 checkpoint_progress_tracker: Option<Arc<CheckpointProgressTracker>>,
156 ) -> Self {
157 Self {
158 epoch_store,
159 state: state.clone(),
160 checkpoint_store,
161 object_cache_reader: state.get_object_cache_reader().clone(),
162 transaction_cache_reader: state.get_transaction_cache_reader().clone(),
163 tx_manager: state.transaction_manager().clone(),
164 global_state_hasher,
165 backpressure_manager,
166 config,
167 metrics,
168 tps_estimator: Mutex::new(TPSEstimator::default()),
169 checkpoint_progress_tracker,
170 data_sender,
171 }
172 }
173
174 pub fn new_for_tests(
175 epoch_store: Arc<AuthorityPerEpochStore>,
176 checkpoint_store: Arc<CheckpointStore>,
177 state: Arc<AuthorityState>,
178 global_state_hasher: Arc<GlobalStateHasher>,
179 ) -> Self {
180 Self::new(
181 epoch_store,
182 checkpoint_store.clone(),
183 state,
184 global_state_hasher,
185 BackpressureManager::new_from_checkpoint_store(&checkpoint_store),
186 Default::default(),
187 CheckpointExecutorMetrics::new_for_tests(),
188 None, None, )
191 }
192
193 fn get_next_to_schedule(&self) -> Option<CheckpointSequenceNumber> {
196 let highest_executed = self
200 .checkpoint_store
201 .get_highest_executed_checkpoint()
202 .unwrap();
203
204 if let Some(highest_executed) = &highest_executed {
205 if self.epoch_store.epoch() == highest_executed.epoch()
206 && highest_executed.is_last_checkpoint_of_epoch()
207 {
208 info!(seq = ?highest_executed.sequence_number, "final checkpoint of epoch has already been executed");
211 return None;
212 }
213 }
214
215 Some(
216 highest_executed
217 .as_ref()
218 .map(|c| c.sequence_number() + 1)
219 .unwrap_or_else(|| {
220 assert_eq!(self.epoch_store.epoch(), 0);
222 0
224 }),
225 )
226 }
227
228 #[instrument(level = "error", skip_all, fields(epoch = ?self.epoch_store.epoch()))]
232 pub async fn run_epoch(self, run_with_range: Option<RunWithRange>) -> StopReason {
233 let _metrics_scope = iota_metrics::monitored_scope("CheckpointExecutor::run_epoch");
234 info!(?run_with_range, "CheckpointExecutor::run_epoch");
235 debug!(
236 "Checkpoint executor running for epoch {:?}",
237 self.epoch_store.epoch(),
238 );
239
240 if run_with_range.is_some_and(|rwr| rwr.is_epoch_gt(self.epoch_store.epoch())) {
244 info!("RunWithRange condition satisfied at {:?}", run_with_range,);
245 return StopReason::RunWithRangeCondition;
246 };
247
248 self.metrics
249 .checkpoint_exec_epoch
250 .set(self.epoch_store.epoch() as i64);
251
252 let Some(next_to_schedule) = self.get_next_to_schedule() else {
253 return StopReason::EpochComplete;
254 };
255
256 let this = Arc::new(self);
257
258 let concurrency = std::env::var("IOTA_CHECKPOINT_EXECUTION_MAX_CONCURRENCY")
259 .ok()
260 .and_then(|s| s.parse().ok())
261 .unwrap_or(this.config.checkpoint_execution_max_concurrency);
262
263 let pipeline_stages = PipelineStages::new(next_to_schedule, this.metrics.clone());
264
265 let final_checkpoint_executed = stream_synced_checkpoints(
266 this.checkpoint_store.clone(),
267 next_to_schedule,
268 run_with_range.and_then(|rwr| rwr.into_checkpoint_bound()),
269 )
270 .map(|checkpoint| {
272 let this = this.clone();
273 let pipeline_handle = pipeline_stages.handle(checkpoint.sequence_number());
274 async move {
275 let pipeline_handle = pipeline_handle.await;
276 tokio::spawn(this.execute_checkpoint(checkpoint, pipeline_handle))
277 .await
278 .unwrap()
279 }
280 })
281 .buffered(concurrency)
282 .fold(false, |state, is_final_checkpoint| async move {
284 assert!(!state, "Cannot execute checkpoint after epoch end");
285 is_final_checkpoint
286 })
287 .await;
288
289 if final_checkpoint_executed {
290 StopReason::EpochComplete
291 } else {
292 StopReason::RunWithRangeCondition
293 }
294 }
295}
296
297impl CheckpointExecutor {
298 #[instrument(level = "debug", skip_all, fields(seq = ?checkpoint.sequence_number()))]
301 async fn execute_checkpoint(
302 self: Arc<Self>,
303 checkpoint: VerifiedCheckpoint,
304 mut pipeline_handle: PipelineHandle,
305 ) -> bool {
306 debug!("executing checkpoint");
307 let sequence_number = checkpoint.sequence_number;
308
309 checkpoint.report_checkpoint_age(&self.metrics.checkpoint_contents_age);
310 self.backpressure_manager
311 .update_highest_certified_checkpoint(sequence_number);
312
313 if checkpoint.is_last_checkpoint_of_epoch() && sequence_number > 0 {
314 let _wait_for_previous_checkpoints_guard =
315 iota_metrics::monitored_scope("CheckpointExecutor::wait_for_previous_checkpoints");
316
317 info!(
318 "Reached end of epoch checkpoint, waiting for all previous checkpoints to be executed"
319 );
320 self.checkpoint_store
321 .notify_read_executed_checkpoint(sequence_number - 1)
322 .await;
323 }
324
325 let _parallel_step_guard =
326 iota_metrics::monitored_scope("CheckpointExecutor::parallel_step");
327
328 let exec_start = Instant::now();
331 let ckpt_state = if self.state.is_fullnode(&self.epoch_store)
332 || checkpoint.is_last_checkpoint_of_epoch()
333 {
334 self.execute_transactions_from_synced_checkpoint(checkpoint, &mut pipeline_handle)
335 .await
336 } else {
337 self.verify_locally_built_checkpoint(checkpoint, &mut pipeline_handle)
338 .await
339 };
340
341 let tps = self.tps_estimator.lock().update(
342 Instant::now(),
343 ckpt_state.data.checkpoint.network_total_transactions,
344 );
345 self.metrics.checkpoint_exec_sync_tps.set(tps as i64);
346
347 self.backpressure_manager
348 .update_highest_executed_checkpoint(ckpt_state.data.checkpoint.sequence_number());
349
350 let is_final_checkpoint = ckpt_state.data.checkpoint.is_last_checkpoint_of_epoch();
351
352 let seq = ckpt_state.data.checkpoint.sequence_number;
353
354 let batch = self.state.get_cache_commit().build_db_batch(
355 self.epoch_store.epoch(),
356 seq,
357 &ckpt_state.data.tx_digests,
358 );
359
360 finish_stage!(pipeline_handle, BuildDbBatch);
361
362 let mut ckpt_state = tokio::task::spawn_blocking({
363 let this = self.clone();
364 move || {
365 let cache_commit = this.state.get_cache_commit();
367 debug!(?seq, "committing checkpoint transactions to disk");
368 cache_commit.commit_transaction_outputs(
369 this.epoch_store.epoch(),
370 batch,
371 &ckpt_state.data.tx_digests,
372 );
373 ckpt_state
374 }
375 })
376 .await
377 .unwrap();
378
379 finish_stage!(pipeline_handle, CommitTransactionOutputs);
380
381 self.epoch_store
382 .handle_finalized_checkpoint(&ckpt_state.data.checkpoint, &ckpt_state.data.tx_digests)
383 .expect("cannot fail");
384
385 let randomness_rounds = self.extract_randomness_rounds(
386 &ckpt_state.data.checkpoint,
387 &ckpt_state.data.checkpoint_contents,
388 );
389
390 if self.state.is_fullnode(&self.epoch_store) {
391 let epoch = ckpt_state.data.checkpoint.epoch;
392 self.epoch_store.remove_shared_version_assignments(
398 randomness_rounds
399 .iter()
400 .map(|round| TransactionKey::RandomnessRound(epoch, *round)),
401 );
402
403 self.epoch_store.remove_shared_version_assignments(
404 ckpt_state
405 .data
406 .tx_digests
407 .iter()
408 .copied()
409 .map(TransactionKey::Digest),
410 );
411 }
412
413 if let Some(randomness_reporter) = self.epoch_store.randomness_reporter() {
418 for round in randomness_rounds {
419 debug!(
420 ?round,
421 "notifying RandomnessReporter that randomness update was executed in checkpoint"
422 );
423 randomness_reporter
424 .notify_randomness_in_checkpoint(round)
425 .expect("epoch cannot have ended");
426 }
427 }
428
429 finish_stage!(pipeline_handle, FinalizeCheckpoint);
430
431 if let Some(checkpoint_data) = ckpt_state.full_data.take() {
432 self.commit_index_updates(checkpoint_data);
433 }
434
435 finish_stage!(pipeline_handle, UpdateRpcIndex);
436
437 self.global_state_hasher
438 .accumulate_running_root(&self.epoch_store, seq, ckpt_state.state_hash)
439 .expect("Failed to accumulate running root");
440
441 if is_final_checkpoint {
442 self.checkpoint_store
443 .insert_epoch_last_checkpoint(self.epoch_store.epoch(), &ckpt_state.data.checkpoint)
444 .expect("Failed to insert epoch last checkpoint");
445
446 self.global_state_hasher
447 .accumulate_epoch(self.epoch_store.clone(), seq)
448 .expect("Accumulating epoch cannot fail");
449
450 self.checkpoint_store
451 .prune_local_summaries()
452 .tap_err(|e| debug_fatal!("Failed to prune local summaries: {}", e))
453 .ok();
454 }
455
456 fail_point!("crash");
457
458 self.bump_highest_executed_checkpoint(&ckpt_state.data.checkpoint);
459
460 self.broadcast_checkpoint(&ckpt_state.data, ckpt_state.full_data.as_ref());
461
462 self.state
465 .pruner()
466 .nudge(ckpt_state.data.checkpoint.sequence_number());
467
468 finish_stage!(pipeline_handle, BumpHighestExecutedCheckpoint);
469
470 if let Some(tracker) = &self.checkpoint_progress_tracker {
471 tracker.add_execution_time(exec_start.elapsed());
472 }
473
474 ckpt_state.data.checkpoint.is_last_checkpoint_of_epoch()
478 }
479
480 #[instrument(level = "info", skip_all)]
483 async fn verify_locally_built_checkpoint(
484 &self,
485 checkpoint: VerifiedCheckpoint,
486 pipeline_handle: &mut PipelineHandle,
487 ) -> CheckpointExecutionState {
488 assert!(
489 !checkpoint.is_last_checkpoint_of_epoch(),
490 "only fullnode path has end-of-epoch logic"
491 );
492
493 let sequence_number = checkpoint.sequence_number;
494 let locally_built_checkpoint = self
495 .checkpoint_store
496 .get_locally_computed_checkpoint(sequence_number)
497 .expect("db error");
498
499 let Some(locally_built_checkpoint) = locally_built_checkpoint else {
500 return self
502 .execute_transactions_from_synced_checkpoint(checkpoint, pipeline_handle)
503 .await;
504 };
505
506 self.metrics.checkpoint_executor_validator_path.inc();
507
508 assert_checkpoint_not_forked(
510 &locally_built_checkpoint,
511 &checkpoint,
512 &self.checkpoint_store,
513 );
514
515 let state_hash = {
518 let _metrics_scope =
519 iota_metrics::monitored_scope("CheckpointExecutor::notify_read_state_hash");
520 self.epoch_store
521 .notify_read_checkpoint_state_hasher(&[sequence_number])
522 .await
523 .unwrap()
524 .pop()
525 .unwrap()
526 };
527
528 let checkpoint_contents = self
532 .checkpoint_store
533 .get_checkpoint_contents(&checkpoint.contents_digest)
534 .expect("db error")
535 .expect("checkpoint contents not found");
536
537 let (tx_digests, fx_digests): (Vec<_>, Vec<_>) = checkpoint_contents
538 .iter()
539 .map(|digests| (digests.transaction, digests.effects))
540 .unzip();
541
542 pipeline_handle
543 .skip_to(PipelineStage::FinalizeTransactions)
544 .await;
545
546 self.insert_finalized_transactions(&tx_digests, sequence_number, checkpoint.timestamp_ms);
550
551 pipeline_handle.skip_to(PipelineStage::BuildDbBatch).await;
552
553 CheckpointExecutionState::new_with_global_state_hash(
554 CheckpointExecutionData {
555 checkpoint,
556 checkpoint_contents,
557 tx_digests,
558 fx_digests,
559 },
560 state_hash,
561 )
562 }
563
564 #[instrument(level = "info", skip_all)]
565 async fn execute_transactions_from_synced_checkpoint(
566 &self,
567 checkpoint: VerifiedCheckpoint,
568 pipeline_handle: &mut PipelineHandle,
569 ) -> CheckpointExecutionState {
570 let sequence_number = checkpoint.sequence_number;
571
572 let (mut ckpt_state, tx_data, unexecuted_tx_digests) = {
573 let _scope = iota_metrics::monitored_scope("CheckpointExecutor::execute_transactions");
574 let (ckpt_state, tx_data) = self.load_checkpoint_transactions(checkpoint);
575 let unexecuted_tx_digests = self.schedule_transaction_execution(&ckpt_state, &tx_data);
576 (ckpt_state, tx_data, unexecuted_tx_digests)
577 };
578
579 finish_stage!(pipeline_handle, ExecuteTransactions);
580
581 {
582 self.transaction_cache_reader
583 .notify_read_executed_effects_digests(
584 "CheckpointExecutor::notify_read_executed_effects_digests",
585 &unexecuted_tx_digests,
586 )
587 .await;
588 }
589
590 finish_stage!(pipeline_handle, WaitForTransactions);
591
592 if ckpt_state.data.checkpoint.is_last_checkpoint_of_epoch() {
593 self.execute_change_epoch_tx(&tx_data).await;
594 }
595
596 let _scope = iota_metrics::monitored_scope("CheckpointExecutor::finalize_checkpoint");
597
598 if self.state.is_fullnode(&self.epoch_store) {
599 self.state.congestion_tracker.process_checkpoint_effects(
600 &*self.transaction_cache_reader,
601 &ckpt_state.data.checkpoint,
602 &tx_data.effects,
603 );
604 }
605
606 self.insert_finalized_transactions(
607 &ckpt_state.data.tx_digests,
608 sequence_number,
609 ckpt_state.data.checkpoint.timestamp_ms,
610 );
611
612 ckpt_state.state_hash = Some(
616 self.global_state_hasher
617 .accumulate_checkpoint(&tx_data.effects, sequence_number, &self.epoch_store)
618 .expect("epoch cannot have ended"),
619 );
620
621 finish_stage!(pipeline_handle, FinalizeTransactions);
622
623 ckpt_state.full_data = self.process_checkpoint_data(&ckpt_state.data, &tx_data);
624
625 finish_stage!(pipeline_handle, ProcessCheckpointData);
626
627 ckpt_state
628 }
629
630 fn checkpoint_data_enabled(&self) -> bool {
631 self.state.grpc_indexes_store.is_some()
632 || self.config.data_ingestion_dir.is_some()
633 || self.data_sender.is_some()
634 }
635
636 fn insert_finalized_transactions(
637 &self,
638 tx_digests: &[TransactionDigest],
639 sequence_number: CheckpointSequenceNumber,
640 timestamp_ms: u64,
641 ) {
642 self.epoch_store
643 .insert_finalized_transactions(tx_digests, sequence_number, timestamp_ms)
644 .expect("failed to insert finalized transactions");
645
646 if self.state.is_fullnode(&self.epoch_store) {
647 self.state
649 .get_checkpoint_cache()
650 .insert_finalized_transactions_perpetual_checkpoints(
651 tx_digests,
652 self.epoch_store.epoch(),
653 sequence_number,
654 );
655 }
656 }
657
658 #[instrument(level = "info", skip_all)]
659 fn process_checkpoint_data(
660 &self,
661 ckpt_data: &CheckpointExecutionData,
662 tx_data: &CheckpointTransactionData,
663 ) -> Option<CheckpointData> {
664 let is_checkpoint_data_enabled = self.checkpoint_data_enabled();
665 let is_last_checkpoint_of_epoch = ckpt_data.checkpoint.is_last_checkpoint_of_epoch();
668 if !is_checkpoint_data_enabled && !is_last_checkpoint_of_epoch {
669 return None;
670 }
671
672 let checkpoint_data = load_checkpoint_data(
673 ckpt_data,
674 tx_data,
675 self.state.get_object_store(),
676 &*self.transaction_cache_reader,
677 )
678 .expect("failed to load checkpoint data");
679
680 if is_last_checkpoint_of_epoch {
689 self.checkpoint_store
690 .index_epoch_boundary(&checkpoint_data)
691 .expect("failed to persist epoch info at boundary");
692 }
693
694 if !is_checkpoint_data_enabled {
695 return None;
697 }
698
699 if let Some(grpc_indexes_store) = &self.state.grpc_indexes_store {
706 grpc_indexes_store.index_checkpoint(&checkpoint_data);
707 }
708
709 if let Some(path) = &self.config.data_ingestion_dir {
710 store_checkpoint_locally(path, &checkpoint_data)
711 .expect("failed to store checkpoint locally");
712 }
713
714 Some(checkpoint_data)
715 }
716
717 #[instrument(level = "info", skip_all)]
719 fn load_checkpoint_transactions(
720 &self,
721 checkpoint: VerifiedCheckpoint,
722 ) -> (CheckpointExecutionState, CheckpointTransactionData) {
723 let seq = checkpoint.sequence_number;
724 let epoch = checkpoint.epoch;
725
726 let checkpoint_contents = self
727 .checkpoint_store
728 .get_checkpoint_contents(&checkpoint.contents_digest)
729 .expect("db error")
730 .expect("checkpoint contents not found");
731
732 if let Some(full_contents) = self
734 .checkpoint_store
735 .get_full_checkpoint_contents_by_sequence_number(seq)
736 .tap_some(|_| debug!("loaded full checkpoint contents in bulk for sequence {seq}"))
737 {
738 let num_txns = full_contents.size();
739 let mut tx_digests = Vec::with_capacity(num_txns);
740 let mut transactions = Vec::with_capacity(num_txns);
741 let mut effects = Vec::with_capacity(num_txns);
742 let mut fx_digests = Vec::with_capacity(num_txns);
743
744 full_contents
745 .iter()
746 .zip(checkpoint_contents.iter())
747 .for_each(|(execution_data, digests)| {
748 let tx_digest = digests.transaction;
749 let fx_digest = digests.effects;
750 debug_assert_eq!(tx_digest, *execution_data.transaction.digest());
751 debug_assert_eq!(fx_digest, execution_data.effects.digest());
752
753 tx_digests.push(tx_digest);
754 transactions.push(VerifiedExecutableTransaction::new_from_checkpoint(
755 VerifiedTransaction::new_unchecked(execution_data.transaction.clone()),
756 epoch,
757 seq,
758 ));
759 effects.push(execution_data.effects.clone());
760 fx_digests.push(fx_digest);
761 });
762
763 let executed_fx_digests = self
764 .transaction_cache_reader
765 .multi_get_executed_effects_digests(&tx_digests);
766
767 (
768 CheckpointExecutionState::new(CheckpointExecutionData {
769 checkpoint,
770 checkpoint_contents,
771 tx_digests,
772 fx_digests,
773 }),
774 CheckpointTransactionData {
775 transactions,
776 effects,
777 executed_fx_digests,
778 },
779 )
780 } else {
781 let digests = checkpoint_contents.transactions();
784
785 let (tx_digests, fx_digests): (Vec<_>, Vec<_>) =
786 digests.iter().map(|d| (d.transaction, d.effects)).unzip();
787 let verified_transactions: Vec<VerifiedTransaction> = self
788 .transaction_cache_reader
789 .multi_get_transaction_blocks(&tx_digests)
790 .into_iter()
791 .enumerate()
792 .map(|(i, tx)| {
793 let tx = tx
794 .unwrap_or_else(|| fatal!("transaction not found for {:?}", tx_digests[i]));
795 Arc::try_unwrap(tx).unwrap_or_else(|tx| (*tx).clone())
796 })
797 .collect();
798 let effects: Vec<TransactionEffects> = self
799 .transaction_cache_reader
800 .multi_get_effects(&fx_digests)
801 .into_iter()
802 .enumerate()
803 .map(|(i, effect)| {
804 effect.unwrap_or_else(|| {
805 fatal!("checkpoint effect not found for {:?}", digests[i])
806 })
807 })
808 .collect();
809
810 if self
819 .checkpoint_store
820 .should_cache_full_checkpoint_contents(seq)
821 {
822 let execution_data = verified_transactions
823 .iter()
824 .zip(effects.iter())
825 .map(|(tx, fx)| ExecutionData::new(tx.clone().into_inner(), fx.clone()));
826 let full_contents = FullCheckpointContents::from_contents_and_execution_data(
827 checkpoint_contents.clone(),
828 execution_data,
829 );
830 self.checkpoint_store.cache_full_checkpoint_contents(
831 seq,
832 checkpoint.contents_digest,
833 full_contents,
834 );
835 }
836
837 let transactions = verified_transactions
838 .into_iter()
839 .map(|tx| VerifiedExecutableTransaction::new_from_checkpoint(tx, epoch, seq))
840 .collect();
841
842 let executed_fx_digests = self
843 .transaction_cache_reader
844 .multi_get_executed_effects_digests(&tx_digests);
845
846 (
847 CheckpointExecutionState::new(CheckpointExecutionData {
848 checkpoint,
849 checkpoint_contents,
850 tx_digests,
851 fx_digests,
852 }),
853 CheckpointTransactionData {
854 transactions,
855 effects,
856 executed_fx_digests,
857 },
858 )
859 }
860 }
861
862 #[instrument(level = "info", skip_all)]
864 fn schedule_transaction_execution(
865 &self,
866 ckpt_state: &CheckpointExecutionState,
867 tx_data: &CheckpointTransactionData,
868 ) -> Vec<TransactionDigest> {
869 let (unexecuted_tx_digests, unexecuted_txns, unexecuted_effects): (Vec<_>, Vec<_>, Vec<_>) =
871 itertools::multiunzip(
872 itertools::izip!(
873 tx_data.transactions.iter(),
874 ckpt_state.data.tx_digests.iter(),
875 ckpt_state.data.fx_digests.iter(),
876 tx_data.effects.iter(),
877 tx_data.executed_fx_digests.iter()
878 )
879 .filter_map(
880 |(txn, tx_digest, expected_fx_digest, effects, executed_fx_digest)| {
881 if let Some(executed_fx_digest) = executed_fx_digest {
882 assert_not_forked(
883 &ckpt_state.data.checkpoint,
884 tx_digest,
885 expected_fx_digest,
886 executed_fx_digest,
887 &*self.transaction_cache_reader,
888 );
889 None
890 } else if txn.transaction().is_end_of_epoch_tx() {
891 None
892 } else {
893 Some((tx_digest, (txn.clone(), *expected_fx_digest), effects))
894 }
895 },
896 ),
897 );
898
899 for ((tx, _), effects) in itertools::izip!(unexecuted_txns.iter(), unexecuted_effects) {
900 if tx.contains_shared_object() {
901 self.epoch_store
902 .acquire_shared_version_assignments_from_effects(
903 tx,
904 effects,
905 &*self.object_cache_reader,
906 )
907 .expect("failed to acquire shared version assignments");
908 }
909 }
910
911 self.tx_manager
913 .enqueue_with_expected_effects_digest(unexecuted_txns, &self.epoch_store);
914
915 unexecuted_tx_digests
916 }
917
918 #[instrument(level = "error", skip_all)]
920 async fn execute_change_epoch_tx(&self, tx_data: &CheckpointTransactionData) {
921 let change_epoch_tx = tx_data.transactions.last().unwrap();
922 let change_epoch_fx = tx_data.effects.last().unwrap();
923 assert_eq!(
924 change_epoch_tx.digest(),
925 change_epoch_fx.transaction_digest()
926 );
927 assert!(
928 change_epoch_tx.transaction().is_end_of_epoch_tx(),
929 "final txn must be an end of epoch txn"
930 );
931
932 self.epoch_store
951 .acquire_shared_version_assignments_from_effects(
952 change_epoch_tx,
953 change_epoch_fx,
954 self.object_cache_reader.as_ref(),
955 )
956 .expect("Acquiring shared version assignments for change_epoch tx cannot fail");
957
958 info!(
959 "scheduling change epoch txn with digest: {:?}, expected effects digest: {:?}",
960 change_epoch_tx.digest(),
961 change_epoch_fx.digest()
962 );
963 self.tx_manager.enqueue_with_expected_effects_digest(
964 vec![(change_epoch_tx.clone(), change_epoch_fx.digest())],
965 &self.epoch_store,
966 );
967
968 self.transaction_cache_reader
969 .notify_read_executed_effects_digests(
970 "CheckpointExecutor::notify_read_advance_epoch_tx",
971 &[*change_epoch_tx.digest()],
972 )
973 .await;
974 }
975
976 #[instrument(level = "debug", skip_all)]
978 fn bump_highest_executed_checkpoint(&self, checkpoint: &VerifiedCheckpoint) {
979 let seq = checkpoint.sequence_number();
981 debug!("Bumping highest_executed_checkpoint watermark to {seq:?}");
982 if let Some(prev_highest) = self
983 .checkpoint_store
984 .get_highest_executed_checkpoint_seq_number()
985 .unwrap()
986 {
987 assert_eq!(prev_highest + 1, seq);
988 } else {
989 assert_eq!(seq, 0);
990 }
991 fail_point!("highest-executed-checkpoint");
992
993 self.checkpoint_store
994 .update_highest_executed_checkpoint(checkpoint)
995 .unwrap();
996 self.metrics.last_executed_checkpoint.set(seq as i64);
997
998 self.metrics
999 .last_executed_checkpoint_timestamp_ms
1000 .set(checkpoint.timestamp_ms as i64);
1001 checkpoint.report_checkpoint_age(&self.metrics.last_executed_checkpoint_age);
1002 }
1003
1004 fn broadcast_checkpoint(
1007 &self,
1008 checkpoint_exec_data: &CheckpointExecutionData,
1009 checkpoint_data: Option<&CheckpointData>,
1010 ) {
1011 if let Some(data_sender) = &self.data_sender {
1012 let checkpoint_data = if let Some(data) = checkpoint_data {
1013 data.clone()
1014 } else {
1015 let (_, tx_data) =
1018 self.load_checkpoint_transactions(checkpoint_exec_data.checkpoint.clone());
1019 load_checkpoint_data(
1020 checkpoint_exec_data,
1021 &tx_data,
1022 self.state.get_object_store(),
1023 self.transaction_cache_reader.as_ref(),
1024 )
1025 .expect("Failed to load full CheckpointData")
1026 };
1027 data_sender(&checkpoint_data);
1028 }
1029
1030 debug!(
1031 "[Fullnode] Full CheckpointData is available: seq={}",
1032 checkpoint_exec_data.checkpoint.sequence_number()
1033 );
1034 }
1035
1036 #[instrument(level = "info", skip_all)]
1039 fn commit_index_updates(&self, checkpoint: CheckpointData) {
1040 if let Some(grpc_indexes_store) = &self.state.grpc_indexes_store {
1041 grpc_indexes_store
1042 .commit_update_for_checkpoint(checkpoint.checkpoint_summary.sequence_number)
1043 .expect("failed to update gRPC indexes");
1044 }
1045 }
1046
1047 #[instrument(level = "debug", skip_all)]
1051 fn extract_randomness_rounds(
1052 &self,
1053 checkpoint: &VerifiedCheckpoint,
1054 checkpoint_contents: &CheckpointContents,
1055 ) -> Vec<RandomnessRound> {
1056 if let Some(version_specific_data) = checkpoint
1057 .parse_version_specific_data(self.epoch_store.protocol_config())
1058 .expect("unable to get version_specific_data")
1059 {
1060 version_specific_data.into_v1().randomness_rounds
1063 } else {
1064 assert_eq!(
1069 0,
1070 self.epoch_store
1071 .protocol_config()
1072 .min_checkpoint_interval_ms_as_option()
1073 .unwrap_or_default(),
1074 );
1075 if let Some(first_digest) = checkpoint_contents.transactions().first() {
1076 let maybe_randomness_tx = self.transaction_cache_reader.get_transaction_block(&first_digest.transaction)
1077 .unwrap_or_else(||
1078 fatal!(
1079 "state-sync should have ensured that transaction with digests {first_digest:?} exists for checkpoint: {}",
1080 checkpoint.sequence_number()
1081 )
1082 );
1083 if let TransactionKind::RandomnessStateUpdate(rsu) =
1084 maybe_randomness_tx.data().transaction().kind()
1085 {
1086 vec![rsu.randomness_round]
1087 } else {
1088 Vec::new()
1089 }
1090 } else {
1091 Vec::new()
1092 }
1093 }
1094 }
1095}