Skip to main content

iota_types/
full_checkpoint_content.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::BTreeMap;
6
7use iota_sdk_types::{
8    ObjectId, ObjectReference, TransactionEffects, TransactionEvents, TransactionKind,
9    checkpoint::CheckpointContents,
10};
11use serde::{Deserialize, Serialize};
12use tap::Pipe;
13
14use crate::{
15    base_types::ExecutionData,
16    effects::{TransactionEffectsAPI, TransactionEffectsExt},
17    iota_system_state::{IotaSystemStateTrait, get_iota_system_state},
18    messages_checkpoint::CertifiedCheckpointSummary,
19    object::Object,
20    storage::{BackingPackageStore, EpochInfo, error::Error as StorageError},
21    transaction::{TransactionDataAPI, TransactionEnvelope},
22};
23
24#[derive(Clone, Debug, Serialize, Deserialize)]
25pub struct CheckpointData {
26    pub checkpoint_summary: CertifiedCheckpointSummary,
27    pub checkpoint_contents: CheckpointContents,
28    pub transactions: Vec<CheckpointTransaction>,
29}
30
31impl CheckpointData {
32    // returns the latest versions of the output objects that still exist at the end
33    // of the checkpoint
34    pub fn latest_live_output_objects(&self) -> Vec<&Object> {
35        let mut latest_live_objects = BTreeMap::new();
36        for tx in self.transactions.iter() {
37            for obj in tx.output_objects.iter() {
38                latest_live_objects.insert(obj.id(), obj);
39            }
40            for obj_ref in tx.removed_object_refs_post_version() {
41                latest_live_objects.remove(&obj_ref.object_id);
42            }
43        }
44        latest_live_objects.into_values().collect()
45    }
46
47    // returns the object refs that are eventually deleted or wrapped in the current
48    // checkpoint
49    pub fn eventually_removed_object_refs_post_version(&self) -> Vec<ObjectReference> {
50        let mut eventually_removed_object_refs = BTreeMap::new();
51        for tx in self.transactions.iter() {
52            for obj_ref in tx.removed_object_refs_post_version() {
53                eventually_removed_object_refs.insert(obj_ref.object_id, obj_ref);
54            }
55            for obj in tx.output_objects.iter() {
56                eventually_removed_object_refs.remove(&(obj.id()));
57            }
58        }
59        eventually_removed_object_refs.into_values().collect()
60    }
61
62    pub fn all_objects(&self) -> Vec<&Object> {
63        self.transactions
64            .iter()
65            .flat_map(|tx| &tx.input_objects)
66            .chain(self.transactions.iter().flat_map(|tx| &tx.output_objects))
67            .collect()
68    }
69
70    /// The transaction that closes this checkpoint's epoch — the `AdvanceEpoch`
71    /// / `advance_epoch_safe_mode` transaction. `None` if this isn't an
72    /// epoch-boundary checkpoint (genesis included), or its last transaction
73    /// unexpectedly isn't an end-of-epoch transaction.
74    pub fn end_of_epoch_transaction(&self) -> Option<&CheckpointTransaction> {
75        // Guard: only epoch-boundary checkpoints carry a closing tx — bail otherwise.
76        self.checkpoint_summary.end_of_epoch_data.as_ref()?;
77        // The epoch-change tx is always ordered last, after every user tx;
78        // verify rather than assume, since callers treat `None` as a hard error.
79        let transaction = self.transactions.last()?;
80        transaction
81            .transaction
82            .transaction()
83            .is_end_of_epoch_tx()
84            .then_some(transaction)
85    }
86
87    /// Returns the epoch boundary information for this checkpoint, paired
88    /// with the events of the transaction that produced this epoch's start
89    /// system state (`EndOfEpoch` for non-genesis checkpoints, `Genesis`
90    /// for checkpoint 0).
91    /// Returns `None` for non-epoch-boundary checkpoints.
92    pub fn epoch_info(
93        &self,
94    ) -> Result<Option<(EpochInfo, Option<TransactionEvents>)>, StorageError> {
95        // If there is no end of epoch data, return None, except for checkpoint 0
96        if self.checkpoint_summary.end_of_epoch_data.is_none()
97            && self.checkpoint_summary.sequence_number != 0
98        {
99            return Ok(None);
100        }
101
102        let (start_checkpoint, transaction) = if self.checkpoint_summary.sequence_number != 0 {
103            let Some(transaction) = self.end_of_epoch_transaction() else {
104                return Err(StorageError::custom(format!(
105                    "Failed to get end of epoch transaction in checkpoint {} with EndOfEpochData",
106                    self.checkpoint_summary.sequence_number,
107                )));
108            };
109            (self.checkpoint_summary.sequence_number + 1, transaction)
110        } else {
111            // For checkpoint 0, we look for the genesis transaction
112            let Some(transaction) = self.transactions.iter().find(|tx| {
113                matches!(
114                    tx.transaction.transaction().kind(),
115                    TransactionKind::Genesis(_)
116                )
117            }) else {
118                return Err(StorageError::custom(format!(
119                    "Failed to get genesis transaction in checkpoint {}",
120                    self.checkpoint_summary.sequence_number,
121                )));
122            };
123            (0, transaction)
124        };
125
126        let system_state =
127            get_iota_system_state(&transaction.output_objects.as_slice()).map_err(|e| {
128                StorageError::custom(format!(
129                    "Failed to find system state object output from end of epoch or genesis transaction: {e}"
130                ))
131            })?;
132
133        Ok(Some((
134            EpochInfo {
135                epoch: system_state.epoch(),
136                protocol_version: system_state.protocol_version(),
137                start_timestamp_ms: system_state.epoch_start_timestamp_ms(),
138                end_timestamp_ms: None,
139                start_checkpoint,
140                end_checkpoint: None,
141                reference_gas_price: system_state.reference_gas_price(),
142                system_state,
143            },
144            transaction.events.clone(),
145        )))
146    }
147}
148
149#[derive(Clone, Debug, Serialize, Deserialize)]
150pub struct CheckpointTransaction {
151    /// The input transaction
152    pub transaction: TransactionEnvelope,
153    /// The effects produced by executing this transaction
154    pub effects: TransactionEffects,
155    /// The events, if any, emitted by this transaction during execution
156    pub events: Option<TransactionEvents>,
157    /// The state of all inputs to this transaction as they were prior to
158    /// execution.
159    pub input_objects: Vec<Object>,
160    /// The state of all output objects created or mutated or unwrapped by this
161    /// transaction.
162    pub output_objects: Vec<Object>,
163}
164
165impl CheckpointTransaction {
166    // provide an iterator over all deleted or wrapped objects in this transaction
167    pub fn removed_objects_pre_version(&self) -> impl Iterator<Item = &Object> {
168        // Since each object ID can only show up once in the input_objects, we can just
169        // use the ids of deleted and wrapped objects to lookup the object in
170        // the input_objects.
171        self.effects
172            .all_removed_objects()
173            .into_iter() // Use id and version to lookup in input Objects
174            .map(|(object_ref, _)| {
175                self.input_objects
176                    .iter()
177                    .find(|o| o.id() == object_ref.object_id)
178                    .expect("all removed objects should show up in input objects")
179            })
180    }
181
182    pub fn removed_object_refs_post_version(&self) -> impl Iterator<Item = ObjectReference> {
183        let deleted = self.effects.deleted().into_iter();
184        let wrapped = self.effects.wrapped().into_iter();
185        let unwrapped_then_deleted = self.effects.unwrapped_then_deleted().into_iter();
186        deleted.chain(wrapped).chain(unwrapped_then_deleted)
187    }
188
189    pub fn changed_objects(&self) -> impl Iterator<Item = (&Object, Option<&Object>)> {
190        self.effects
191            .all_changed_objects()
192            .into_iter()
193            .map(|(object_ref, ..)| {
194                let object = self
195                    .output_objects
196                    .iter()
197                    .find(|o| o.id() == object_ref.object_id)
198                    .expect("changed objects should show up in output objects");
199
200                let old_object = self
201                    .input_objects
202                    .iter()
203                    .find(|o| o.id() == object_ref.object_id);
204
205                (object, old_object)
206            })
207    }
208
209    pub fn created_objects(&self) -> impl Iterator<Item = &Object> {
210        // Iterator over (ObjectId, version) for created objects
211        self.effects
212            .created()
213            .into_iter()
214            // Lookup Objects in output Objects as well as old versions for mutated objects
215            .map(|(object_ref, _)| {
216                self.output_objects
217                    .iter()
218                    .find(|o| o.id() == object_ref.object_id && o.version() == object_ref.version)
219                    .expect("created objects should show up in output objects")
220            })
221    }
222
223    pub fn execution_data(&self) -> ExecutionData {
224        ExecutionData {
225            transaction: self.transaction.clone(),
226            effects: self.effects.clone(),
227        }
228    }
229}
230
231impl BackingPackageStore for CheckpointData {
232    fn get_package_object(
233        &self,
234        package_id: &ObjectId,
235    ) -> crate::error::IotaResult<Option<crate::storage::PackageObject>> {
236        self.transactions
237            .iter()
238            .flat_map(|transaction| transaction.output_objects.iter())
239            .find(|object| object.is_package() && &object.id() == package_id)
240            .cloned()
241            .map(crate::storage::PackageObject::new)
242            .pipe(Ok)
243    }
244}