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::{TransactionEffects, TransactionEffectsAPI, TransactionEvents},
13    iota_system_state::{IotaSystemStateTrait, get_iota_system_state},
14    messages_checkpoint::{CertifiedCheckpointSummary, CheckpointContents},
15    object::Object,
16    storage::{BackingPackageStore, EpochInfo, error::Error as StorageError},
17    transaction::{Transaction, TransactionDataAPI, TransactionKind},
18};
19
20#[derive(Clone, Debug, Serialize, Deserialize)]
21pub struct CheckpointData {
22    pub checkpoint_summary: CertifiedCheckpointSummary,
23    pub checkpoint_contents: CheckpointContents,
24    pub transactions: Vec<CheckpointTransaction>,
25}
26
27impl CheckpointData {
28    // returns the latest versions of the output objects that still exist at the end
29    // of the checkpoint
30    pub fn latest_live_output_objects(&self) -> Vec<&Object> {
31        let mut latest_live_objects = BTreeMap::new();
32        for tx in self.transactions.iter() {
33            for obj in tx.output_objects.iter() {
34                latest_live_objects.insert(obj.id(), obj);
35            }
36            for obj_ref in tx.removed_object_refs_post_version() {
37                latest_live_objects.remove(&(obj_ref.0));
38            }
39        }
40        latest_live_objects.into_values().collect()
41    }
42
43    // returns the object refs that are eventually deleted or wrapped in the current
44    // checkpoint
45    pub fn eventually_removed_object_refs_post_version(&self) -> Vec<ObjectRef> {
46        let mut eventually_removed_object_refs = BTreeMap::new();
47        for tx in self.transactions.iter() {
48            for obj_ref in tx.removed_object_refs_post_version() {
49                eventually_removed_object_refs.insert(obj_ref.0, obj_ref);
50            }
51            for obj in tx.output_objects.iter() {
52                eventually_removed_object_refs.remove(&(obj.id()));
53            }
54        }
55        eventually_removed_object_refs.into_values().collect()
56    }
57
58    /// Returns all objects that are used as input to the transactions in the
59    /// checkpoint, and already exist prior to the checkpoint.
60    pub fn checkpoint_input_objects(&self) -> BTreeMap<ObjectID, &Object> {
61        let mut output_objects_seen = HashSet::new();
62        let mut checkpoint_input_objects = BTreeMap::new();
63        for tx in self.transactions.iter() {
64            for obj in tx.input_objects.iter() {
65                let id = obj.id();
66                if output_objects_seen.contains(&id) || checkpoint_input_objects.contains_key(&id) {
67                    continue;
68                }
69                checkpoint_input_objects.insert(id, obj);
70            }
71            for obj in tx.output_objects.iter() {
72                // We want to track input objects that are not output objects
73                // in the previous transactions.
74                output_objects_seen.insert(obj.id());
75            }
76        }
77        checkpoint_input_objects
78    }
79
80    pub fn all_objects(&self) -> Vec<&Object> {
81        self.transactions
82            .iter()
83            .flat_map(|tx| &tx.input_objects)
84            .chain(self.transactions.iter().flat_map(|tx| &tx.output_objects))
85            .collect()
86    }
87
88    pub fn epoch_info(&self) -> Result<Option<EpochInfo>, StorageError> {
89        // If there is no end of epoch data, return None, except for checkpoint 0
90        if self.checkpoint_summary.end_of_epoch_data.is_none()
91            && self.checkpoint_summary.sequence_number != 0
92        {
93            return Ok(None);
94        }
95
96        let (start_checkpoint, transaction) = if self.checkpoint_summary.sequence_number != 0 {
97            let Some(transaction) = self.transactions.iter().find(|tx| {
98                matches!(
99                    tx.transaction.intent_message().value.kind(),
100                    TransactionKind::EndOfEpochTransaction(_)
101                )
102            }) else {
103                return Err(StorageError::custom(format!(
104                    "Failed to get end of epoch transaction in checkpoint {} with EndOfEpochData",
105                    self.checkpoint_summary.sequence_number,
106                )));
107            };
108            (self.checkpoint_summary.sequence_number + 1, transaction)
109        } else {
110            // For checkpoint 0, we look for the genesis transaction
111            let Some(transaction) = self.transactions.iter().find(|tx| {
112                matches!(
113                    tx.transaction.intent_message().value.kind(),
114                    TransactionKind::Genesis(_)
115                )
116            }) else {
117                return Err(StorageError::custom(format!(
118                    "Failed to get genesis transaction in checkpoint {}",
119                    self.checkpoint_summary.sequence_number,
120                )));
121            };
122            (0, transaction)
123        };
124
125        let system_state =
126            get_iota_system_state(&transaction.output_objects.as_slice()).map_err(|e| {
127                StorageError::custom(format!(
128                    "Failed to find system state object output from end of epoch or genesis transaction: {e}"
129                ))
130            })?;
131
132        Ok(Some(EpochInfo {
133            epoch: system_state.epoch(),
134            protocol_version: system_state.protocol_version(),
135            start_timestamp_ms: system_state.epoch_start_timestamp_ms(),
136            end_timestamp_ms: None,
137            start_checkpoint,
138            end_checkpoint: None,
139            reference_gas_price: system_state.reference_gas_price(),
140            system_state,
141        }))
142    }
143}
144
145#[derive(Clone, Debug, Serialize, Deserialize)]
146pub struct CheckpointTransaction {
147    /// The input Transaction
148    pub transaction: Transaction,
149    /// The effects produced by executing this transaction
150    pub effects: TransactionEffects,
151    /// The events, if any, emitted by this transaction during execution
152    pub events: Option<TransactionEvents>,
153    /// The state of all inputs to this transaction as they were prior to
154    /// execution.
155    pub input_objects: Vec<Object>,
156    /// The state of all output objects created or mutated or unwrapped by this
157    /// transaction.
158    pub output_objects: Vec<Object>,
159}
160
161impl CheckpointTransaction {
162    // provide an iterator over all deleted or wrapped objects in this transaction
163    pub fn removed_objects_pre_version(&self) -> impl Iterator<Item = &Object> {
164        // Since each object ID can only show up once in the input_objects, we can just
165        // use the ids of deleted and wrapped objects to lookup the object in
166        // the input_objects.
167        self.effects
168            .all_removed_objects()
169            .into_iter() // Use id and version to lookup in input Objects
170            .map(|((id, _, _), _)| {
171                self.input_objects
172                    .iter()
173                    .find(|o| o.id() == id)
174                    .expect("all removed objects should show up in input objects")
175            })
176    }
177
178    pub fn removed_object_refs_post_version(&self) -> impl Iterator<Item = ObjectRef> {
179        let deleted = self.effects.deleted().into_iter();
180        let wrapped = self.effects.wrapped().into_iter();
181        let unwrapped_then_deleted = self.effects.unwrapped_then_deleted().into_iter();
182        deleted.chain(wrapped).chain(unwrapped_then_deleted)
183    }
184
185    pub fn changed_objects(&self) -> impl Iterator<Item = (&Object, Option<&Object>)> {
186        self.effects
187            .all_changed_objects()
188            .into_iter()
189            .map(|((id, _, _), ..)| {
190                let object = self
191                    .output_objects
192                    .iter()
193                    .find(|o| o.id() == id)
194                    .expect("changed objects should show up in output objects");
195
196                let old_object = self.input_objects.iter().find(|o| o.id() == id);
197
198                (object, old_object)
199            })
200    }
201
202    pub fn created_objects(&self) -> impl Iterator<Item = &Object> {
203        // Iterator over (ObjectId, version) for created objects
204        self.effects
205            .created()
206            .into_iter()
207            // Lookup Objects in output Objects as well as old versions for mutated objects
208            .map(|((id, version, _), _)| {
209                self.output_objects
210                    .iter()
211                    .find(|o| o.id() == id && o.version() == version)
212                    .expect("created objects should show up in output objects")
213            })
214    }
215}
216
217impl BackingPackageStore for CheckpointData {
218    fn get_package_object(
219        &self,
220        package_id: &crate::base_types::ObjectID,
221    ) -> crate::error::IotaResult<Option<crate::storage::PackageObject>> {
222        self.transactions
223            .iter()
224            .flat_map(|transaction| transaction.output_objects.iter())
225            .find(|object| object.is_package() && &object.id() == package_id)
226            .cloned()
227            .map(crate::storage::PackageObject::new)
228            .pipe(Ok)
229    }
230}