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