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, HashSet};
6
7use serde::{Deserialize, Serialize};
8use tap::Pipe;
9
10use crate::{
11    base_types::{ObjectID, ObjectRef},
12    effects::{
13        IDOperation, ObjectIn, ObjectOut, TransactionEffects, TransactionEffectsAPI,
14        TransactionEvents,
15    },
16    iota_system_state::{IotaSystemStateTrait, get_iota_system_state},
17    messages_checkpoint::{CertifiedCheckpointSummary, CheckpointContents},
18    object::Object,
19    storage::{BackingPackageStore, EpochInfo, error::Error as StorageError},
20    transaction::{Transaction, TransactionDataAPI, TransactionKind},
21};
22
23#[derive(Clone, Debug, Serialize, Deserialize)]
24pub struct CheckpointData {
25    pub checkpoint_summary: CertifiedCheckpointSummary,
26    pub checkpoint_contents: CheckpointContents,
27    pub transactions: Vec<CheckpointTransaction>,
28}
29
30impl CheckpointData {
31    // returns the latest versions of the output objects that still exist at the end
32    // of the checkpoint
33    pub fn latest_live_output_objects(&self) -> Vec<&Object> {
34        let mut latest_live_objects = BTreeMap::new();
35        for tx in self.transactions.iter() {
36            for obj in tx.output_objects.iter() {
37                latest_live_objects.insert(obj.id(), obj);
38            }
39            for obj_ref in tx.removed_object_refs_post_version() {
40                latest_live_objects.remove(&(obj_ref.0));
41            }
42        }
43        latest_live_objects.into_values().collect()
44    }
45
46    // returns the object refs that are eventually deleted or wrapped in the current
47    // checkpoint
48    pub fn eventually_removed_object_refs_post_version(&self) -> Vec<ObjectRef> {
49        let mut eventually_removed_object_refs = BTreeMap::new();
50        for tx in self.transactions.iter() {
51            for obj_ref in tx.removed_object_refs_post_version() {
52                eventually_removed_object_refs.insert(obj_ref.0, obj_ref);
53            }
54            for obj in tx.output_objects.iter() {
55                eventually_removed_object_refs.remove(&(obj.id()));
56            }
57        }
58        eventually_removed_object_refs.into_values().collect()
59    }
60
61    /// Returns all objects that are used as input to the transactions in the
62    /// checkpoint, and already exist prior to the checkpoint.
63    pub fn checkpoint_input_objects(&self) -> BTreeMap<ObjectID, &Object> {
64        let mut output_objects_seen = HashSet::new();
65        let mut checkpoint_input_objects = BTreeMap::new();
66        for tx in self.transactions.iter() {
67            for obj in tx.input_objects.iter() {
68                let id = obj.id();
69                if output_objects_seen.contains(&id) || checkpoint_input_objects.contains_key(&id) {
70                    continue;
71                }
72                checkpoint_input_objects.insert(id, obj);
73            }
74            for obj in tx.output_objects.iter() {
75                // We want to track input objects that are not output objects
76                // in the previous transactions.
77                output_objects_seen.insert(obj.id());
78            }
79        }
80        checkpoint_input_objects
81    }
82
83    pub fn all_objects(&self) -> Vec<&Object> {
84        self.transactions
85            .iter()
86            .flat_map(|tx| &tx.input_objects)
87            .chain(self.transactions.iter().flat_map(|tx| &tx.output_objects))
88            .collect()
89    }
90
91    pub fn epoch_info(&self) -> Result<Option<EpochInfo>, StorageError> {
92        // If there is no end of epoch data, return None, except for checkpoint 0
93        if self.checkpoint_summary.end_of_epoch_data.is_none()
94            && self.checkpoint_summary.sequence_number != 0
95        {
96            return Ok(None);
97        }
98
99        let (start_checkpoint, transaction) = if self.checkpoint_summary.sequence_number != 0 {
100            let Some(transaction) = self.transactions.iter().find(|tx| {
101                matches!(
102                    tx.transaction.intent_message().value.kind(),
103                    TransactionKind::EndOfEpochTransaction(_)
104                )
105            }) else {
106                return Err(StorageError::custom(format!(
107                    "Failed to get end of epoch transaction in checkpoint {} with EndOfEpochData",
108                    self.checkpoint_summary.sequence_number,
109                )));
110            };
111            (self.checkpoint_summary.sequence_number + 1, transaction)
112        } else {
113            // For checkpoint 0, we look for the genesis transaction
114            let Some(transaction) = self.transactions.iter().find(|tx| {
115                matches!(
116                    tx.transaction.intent_message().value.kind(),
117                    TransactionKind::Genesis(_)
118                )
119            }) else {
120                return Err(StorageError::custom(format!(
121                    "Failed to get genesis transaction in checkpoint {}",
122                    self.checkpoint_summary.sequence_number,
123                )));
124            };
125            (0, transaction)
126        };
127
128        let system_state =
129            get_iota_system_state(&transaction.output_objects.as_slice()).map_err(|e| {
130                StorageError::custom(format!(
131                    "Failed to find system state object output from end of epoch or genesis transaction: {e}"
132                ))
133            })?;
134
135        Ok(Some(EpochInfo {
136            epoch: system_state.epoch(),
137            protocol_version: system_state.protocol_version(),
138            start_timestamp_ms: system_state.epoch_start_timestamp_ms(),
139            end_timestamp_ms: None,
140            start_checkpoint,
141            end_checkpoint: None,
142            reference_gas_price: system_state.reference_gas_price(),
143            system_state,
144        }))
145    }
146}
147
148#[derive(Clone, Debug, Serialize, Deserialize)]
149pub struct CheckpointTransaction {
150    /// The input Transaction
151    pub transaction: Transaction,
152    /// The effects produced by executing this transaction
153    pub effects: TransactionEffects,
154    /// The events, if any, emitted by this transaction during execution
155    pub events: Option<TransactionEvents>,
156    /// The state of all inputs to this transaction as they were prior to
157    /// execution.
158    pub input_objects: Vec<Object>,
159    /// The state of all output objects created or mutated or unwrapped by this
160    /// transaction.
161    pub output_objects: Vec<Object>,
162}
163
164impl CheckpointTransaction {
165    // provide an iterator over all deleted or wrapped objects in this transaction
166    pub fn removed_objects_pre_version(&self) -> impl Iterator<Item = &Object> {
167        // Iterator over id and versions for all deleted or wrapped objects
168        match &self.effects {
169            TransactionEffects::V1(v1) => {
170                v1.changed_objects().iter().filter_map(|(id, change)| {
171                    match (
172                        &change.input_state,
173                        &change.output_state,
174                        &change.id_operation,
175                    ) {
176                        // Deleted Objects
177                        (
178                            ObjectIn::Exist(((version, _d), _o)),
179                            ObjectOut::NotExist,
180                            IDOperation::Deleted,
181                        ) => Some((id, version)),
182
183                        // Wrapped Objects
184                        (
185                            ObjectIn::Exist(((version, _), _)),
186                            ObjectOut::NotExist,
187                            IDOperation::None,
188                        ) => Some((id, version)),
189                        _ => None,
190                    }
191                })
192            }
193        }
194        // Use id and version to lookup in input Objects
195        .map(|(id, version)| {
196            self.input_objects
197                .iter()
198                .find(|o| &o.id() == id && &o.version() == version)
199                .expect("all removed objects should show up in input objects")
200        })
201    }
202
203    pub fn removed_object_refs_post_version(&self) -> impl Iterator<Item = ObjectRef> {
204        let deleted = self.effects.deleted().into_iter();
205        let wrapped = self.effects.wrapped().into_iter();
206        let unwrapped_then_deleted = self.effects.unwrapped_then_deleted().into_iter();
207        deleted.chain(wrapped).chain(unwrapped_then_deleted)
208    }
209
210    pub fn changed_objects(&self) -> impl Iterator<Item = (&Object, Option<&Object>)> {
211        // Iterator over ((ObjectId, new version), Option<old version>)
212        match &self.effects {
213            TransactionEffects::V1(v1) => {
214                v1.changed_objects().iter().filter_map(|(id, change)| {
215                    match (
216                        &change.input_state,
217                        &change.output_state,
218                        &change.id_operation,
219                    ) {
220                        // Created Objects
221                        (ObjectIn::NotExist, ObjectOut::ObjectWrite(_), IDOperation::Created) => {
222                            Some(((id, &v1.lamport_version), None))
223                        }
224                        (
225                            ObjectIn::NotExist,
226                            ObjectOut::PackageWrite((version, _)),
227                            IDOperation::Created,
228                        ) => Some(((id, version), None)),
229
230                        // Unwrapped Objects
231                        (ObjectIn::NotExist, ObjectOut::ObjectWrite(_), IDOperation::None) => {
232                            Some(((id, &v1.lamport_version), None))
233                        }
234
235                        // Mutated Objects
236                        (ObjectIn::Exist(((old_version, _), _)), ObjectOut::ObjectWrite(_), _) => {
237                            Some(((id, &v1.lamport_version), Some(old_version)))
238                        }
239                        (
240                            ObjectIn::Exist(((old_version, _), _)),
241                            ObjectOut::PackageWrite((version, _)),
242                            _,
243                        ) => Some(((id, version), Some(old_version))),
244
245                        _ => None,
246                    }
247                })
248            }
249        }
250        // Lookup Objects in output Objects as well as old versions for mutated objects
251        .map(|((id, version), old_version)| {
252            let object = self
253                .output_objects
254                .iter()
255                .find(|o| &o.id() == id && &o.version() == version)
256                .expect("changed objects should show up in output objects");
257
258            let old_object = old_version.map(|old_version| {
259                self.input_objects
260                    .iter()
261                    .find(|o| &o.id() == id && &o.version() == old_version)
262                    .expect("mutated objects should have a previous version in input objects")
263            });
264
265            (object, old_object)
266        })
267    }
268
269    pub fn created_objects(&self) -> impl Iterator<Item = &Object> {
270        // Iterator over (ObjectId, version) for created objects
271        match &self.effects {
272            TransactionEffects::V1(v1) => {
273                v1.changed_objects().iter().filter_map(|(id, change)| {
274                    match (
275                        &change.input_state,
276                        &change.output_state,
277                        &change.id_operation,
278                    ) {
279                        // Created Objects
280                        (ObjectIn::NotExist, ObjectOut::ObjectWrite(_), IDOperation::Created) => {
281                            Some((id, &v1.lamport_version))
282                        }
283                        (
284                            ObjectIn::NotExist,
285                            ObjectOut::PackageWrite((version, _)),
286                            IDOperation::Created,
287                        ) => Some((id, version)),
288
289                        _ => None,
290                    }
291                })
292            }
293        }
294        // Lookup Objects in output Objects as well as old versions for mutated objects
295        .map(|(id, version)| {
296            self.output_objects
297                .iter()
298                .find(|o| &o.id() == id && &o.version() == version)
299                .expect("created objects should show up in output objects")
300        })
301    }
302}
303
304impl BackingPackageStore for CheckpointData {
305    fn get_package_object(
306        &self,
307        package_id: &crate::base_types::ObjectID,
308    ) -> crate::error::IotaResult<Option<crate::storage::PackageObject>> {
309        self.transactions
310            .iter()
311            .flat_map(|transaction| transaction.output_objects.iter())
312            .find(|object| object.is_package() && &object.id() == package_id)
313            .cloned()
314            .map(crate::storage::PackageObject::new)
315            .pipe(Ok)
316    }
317}