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::{ObjectId, TransactionKind};
8use serde::{Deserialize, Serialize};
9use tap::Pipe;
10
11use crate::{
12    base_types::ObjectRef,
13    effects::{
14        TransactionEffects, TransactionEffectsAPI, TransactionEffectsExt, 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},
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.object_id);
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.object_id, 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    pub fn all_objects(&self) -> Vec<&Object> {
62        self.transactions
63            .iter()
64            .flat_map(|tx| &tx.input_objects)
65            .chain(self.transactions.iter().flat_map(|tx| &tx.output_objects))
66            .collect()
67    }
68
69    pub fn epoch_info(&self) -> Result<Option<EpochInfo>, StorageError> {
70        // If there is no end of epoch data, return None, except for checkpoint 0
71        if self.checkpoint_summary.end_of_epoch_data.is_none()
72            && self.checkpoint_summary.sequence_number != 0
73        {
74            return Ok(None);
75        }
76
77        let (start_checkpoint, transaction) = if self.checkpoint_summary.sequence_number != 0 {
78            let Some(transaction) = self.transactions.iter().find(|tx| {
79                matches!(
80                    tx.transaction.intent_message().value.kind(),
81                    TransactionKind::EndOfEpoch(_)
82                )
83            }) else {
84                return Err(StorageError::custom(format!(
85                    "Failed to get end of epoch transaction in checkpoint {} with EndOfEpochData",
86                    self.checkpoint_summary.sequence_number,
87                )));
88            };
89            (self.checkpoint_summary.sequence_number + 1, transaction)
90        } else {
91            // For checkpoint 0, we look for the genesis transaction
92            let Some(transaction) = self.transactions.iter().find(|tx| {
93                matches!(
94                    tx.transaction.intent_message().value.kind(),
95                    TransactionKind::Genesis(_)
96                )
97            }) else {
98                return Err(StorageError::custom(format!(
99                    "Failed to get genesis transaction in checkpoint {}",
100                    self.checkpoint_summary.sequence_number,
101                )));
102            };
103            (0, transaction)
104        };
105
106        let system_state =
107            get_iota_system_state(&transaction.output_objects.as_slice()).map_err(|e| {
108                StorageError::custom(format!(
109                    "Failed to find system state object output from end of epoch or genesis transaction: {e}"
110                ))
111            })?;
112
113        Ok(Some(EpochInfo {
114            epoch: system_state.epoch(),
115            protocol_version: system_state.protocol_version(),
116            start_timestamp_ms: system_state.epoch_start_timestamp_ms(),
117            end_timestamp_ms: None,
118            start_checkpoint,
119            end_checkpoint: None,
120            reference_gas_price: system_state.reference_gas_price(),
121            system_state,
122        }))
123    }
124}
125
126#[derive(Clone, Debug, Serialize, Deserialize)]
127pub struct CheckpointTransaction {
128    /// The input Transaction
129    pub transaction: Transaction,
130    /// The effects produced by executing this transaction
131    pub effects: TransactionEffects,
132    /// The events, if any, emitted by this transaction during execution
133    pub events: Option<TransactionEvents>,
134    /// The state of all inputs to this transaction as they were prior to
135    /// execution.
136    pub input_objects: Vec<Object>,
137    /// The state of all output objects created or mutated or unwrapped by this
138    /// transaction.
139    pub output_objects: Vec<Object>,
140}
141
142impl CheckpointTransaction {
143    // provide an iterator over all deleted or wrapped objects in this transaction
144    pub fn removed_objects_pre_version(&self) -> impl Iterator<Item = &Object> {
145        // Since each object ID can only show up once in the input_objects, we can just
146        // use the ids of deleted and wrapped objects to lookup the object in
147        // the input_objects.
148        self.effects
149            .all_removed_objects()
150            .into_iter() // Use id and version to lookup in input Objects
151            .map(|(object_ref, _)| {
152                self.input_objects
153                    .iter()
154                    .find(|o| o.id() == object_ref.object_id)
155                    .expect("all removed objects should show up in input objects")
156            })
157    }
158
159    pub fn removed_object_refs_post_version(&self) -> impl Iterator<Item = ObjectRef> {
160        let deleted = self.effects.deleted().into_iter();
161        let wrapped = self.effects.wrapped().into_iter();
162        let unwrapped_then_deleted = self.effects.unwrapped_then_deleted().into_iter();
163        deleted.chain(wrapped).chain(unwrapped_then_deleted)
164    }
165
166    pub fn changed_objects(&self) -> impl Iterator<Item = (&Object, Option<&Object>)> {
167        self.effects
168            .all_changed_objects()
169            .into_iter()
170            .map(|(object_ref, ..)| {
171                let object = self
172                    .output_objects
173                    .iter()
174                    .find(|o| o.id() == object_ref.object_id)
175                    .expect("changed objects should show up in output objects");
176
177                let old_object = self
178                    .input_objects
179                    .iter()
180                    .find(|o| o.id() == object_ref.object_id);
181
182                (object, old_object)
183            })
184    }
185
186    pub fn created_objects(&self) -> impl Iterator<Item = &Object> {
187        // Iterator over (ObjectId, version) for created objects
188        self.effects
189            .created()
190            .into_iter()
191            // Lookup Objects in output Objects as well as old versions for mutated objects
192            .map(|(object_ref, _)| {
193                self.output_objects
194                    .iter()
195                    .find(|o| o.id() == object_ref.object_id && o.version() == object_ref.version)
196                    .expect("created objects should show up in output objects")
197            })
198    }
199}
200
201impl BackingPackageStore for CheckpointData {
202    fn get_package_object(
203        &self,
204        package_id: &ObjectId,
205    ) -> crate::error::IotaResult<Option<crate::storage::PackageObject>> {
206        self.transactions
207            .iter()
208            .flat_map(|transaction| transaction.output_objects.iter())
209            .find(|object| object.is_package() && &object.id() == package_id)
210            .cloned()
211            .map(crate::storage::PackageObject::new)
212            .pipe(Ok)
213    }
214}