sui_types/
full_checkpoint_content.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::BTreeMap;
5
6use crate::base_types::{ExecutionData, ObjectID, ObjectRef};
7use crate::effects::{TransactionEffects, TransactionEffectsAPI, TransactionEvents};
8use crate::messages_checkpoint::{CertifiedCheckpointSummary, CheckpointContents};
9use crate::object::Object;
10use crate::signature::GenericSignature;
11use crate::storage::ObjectKey;
12use crate::storage::error::Error as StorageError;
13use crate::storage::{BackingPackageStore, EpochInfo};
14use crate::sui_system_state::SuiSystemStateTrait;
15use crate::sui_system_state::get_sui_system_state;
16use crate::transaction::{Transaction, TransactionData, TransactionDataAPI, TransactionKind};
17use serde::{Deserialize, Serialize};
18use tap::Pipe;
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 of the checkpoint
29    pub fn latest_live_output_objects(&self) -> Vec<&Object> {
30        let mut latest_live_objects = BTreeMap::new();
31        for tx in self.transactions.iter() {
32            for obj in tx.output_objects.iter() {
33                latest_live_objects.insert(obj.id(), obj);
34            }
35            for obj_ref in tx.removed_object_refs_post_version() {
36                latest_live_objects.remove(&(obj_ref.0));
37            }
38        }
39        latest_live_objects.into_values().collect()
40    }
41
42    // returns the object refs that are eventually deleted or wrapped in the current checkpoint
43    pub fn eventually_removed_object_refs_post_version(&self) -> Vec<ObjectRef> {
44        let mut eventually_removed_object_refs = BTreeMap::new();
45        for tx in self.transactions.iter() {
46            for obj_ref in tx.removed_object_refs_post_version() {
47                eventually_removed_object_refs.insert(obj_ref.0, obj_ref);
48            }
49            for obj in tx.output_objects.iter() {
50                eventually_removed_object_refs.remove(&(obj.id()));
51            }
52        }
53        eventually_removed_object_refs.into_values().collect()
54    }
55
56    pub fn all_objects(&self) -> Vec<&Object> {
57        self.transactions
58            .iter()
59            .flat_map(|tx| &tx.input_objects)
60            .chain(self.transactions.iter().flat_map(|tx| &tx.output_objects))
61            .collect()
62    }
63
64    pub fn epoch_info(&self) -> Result<Option<EpochInfo>, StorageError> {
65        if self.checkpoint_summary.end_of_epoch_data.is_none()
66            && self.checkpoint_summary.sequence_number != 0
67        {
68            return Ok(None);
69        }
70        let (start_checkpoint, transaction) = if self.checkpoint_summary.sequence_number == 0 {
71            (0, &self.transactions[0])
72        } else {
73            let Some(transaction) = self.transactions.iter().find(|tx| {
74                matches!(
75                    tx.transaction.intent_message().value.kind(),
76                    TransactionKind::ChangeEpoch(_) | TransactionKind::EndOfEpochTransaction(_)
77                )
78            }) else {
79                return Err(StorageError::custom(format!(
80                    "Failed to get end of epoch transaction in checkpoint {} with EndOfEpochData",
81                    self.checkpoint_summary.sequence_number,
82                )));
83            };
84            (self.checkpoint_summary.sequence_number + 1, transaction)
85        };
86        let system_state =
87            get_sui_system_state(&transaction.output_objects.as_slice()).map_err(|e| {
88                StorageError::custom(format!(
89                    "Failed to find system state object output from end of epoch transaction: {e}"
90                ))
91            })?;
92        Ok(Some(EpochInfo {
93            epoch: system_state.epoch(),
94            protocol_version: Some(system_state.protocol_version()),
95            start_timestamp_ms: Some(system_state.epoch_start_timestamp_ms()),
96            end_timestamp_ms: None,
97            start_checkpoint: Some(start_checkpoint),
98            end_checkpoint: None,
99            reference_gas_price: Some(system_state.reference_gas_price()),
100            system_state: Some(system_state),
101        }))
102    }
103}
104
105#[derive(Clone, Debug, Serialize, Deserialize)]
106pub struct CheckpointTransaction {
107    /// The input Transaction
108    pub transaction: Transaction,
109    /// The effects produced by executing this transaction
110    pub effects: TransactionEffects,
111    /// The events, if any, emitted by this transactions during execution
112    pub events: Option<TransactionEvents>,
113    /// The state of all inputs to this transaction as they were prior to execution.
114    pub input_objects: Vec<Object>,
115    /// The state of all output objects created or mutated or unwrapped by this transaction.
116    pub output_objects: Vec<Object>,
117}
118
119impl CheckpointTransaction {
120    // provide an iterator over all deleted or wrapped objects in this transaction
121    pub fn removed_objects_pre_version(&self) -> impl Iterator<Item = &Object> {
122        // Since each object ID can only show up once in the input_objects, we can just use the
123        // ids of deleted and wrapped objects to lookup the object in the input_objects.
124        self.effects
125            .all_removed_objects()
126            .into_iter() // Use id and version to lookup in input Objects
127            .map(|((id, _, _), _)| {
128                self.input_objects
129                    .iter()
130                    .find(|o| o.id() == id)
131                    .expect("all removed objects should show up in input objects")
132            })
133    }
134
135    pub fn removed_object_refs_post_version(&self) -> impl Iterator<Item = ObjectRef> {
136        let deleted = self.effects.deleted().into_iter();
137        let wrapped = self.effects.wrapped().into_iter();
138        let unwrapped_then_deleted = self.effects.unwrapped_then_deleted().into_iter();
139        deleted.chain(wrapped).chain(unwrapped_then_deleted)
140    }
141
142    pub fn changed_objects(&self) -> impl Iterator<Item = (&Object, Option<&Object>)> {
143        self.effects
144            .all_changed_objects()
145            .into_iter()
146            .map(|((id, _, _), ..)| {
147                let object = self
148                    .output_objects
149                    .iter()
150                    .find(|o| o.id() == id)
151                    .expect("changed objects should show up in output objects");
152
153                let old_object = self.input_objects.iter().find(|o| o.id() == id);
154
155                (object, old_object)
156            })
157    }
158
159    pub fn created_objects(&self) -> impl Iterator<Item = &Object> {
160        // Iterator over (ObjectId, version) for created objects
161        self.effects
162            .created()
163            .into_iter()
164            // Lookup Objects in output Objects as well as old versions for mutated objects
165            .map(|((id, version, _), _)| {
166                self.output_objects
167                    .iter()
168                    .find(|o| o.id() == id && o.version() == version)
169                    .expect("created objects should show up in output objects")
170            })
171    }
172
173    pub fn execution_data(&self) -> ExecutionData {
174        ExecutionData {
175            transaction: self.transaction.clone(),
176            effects: self.effects.clone(),
177        }
178    }
179}
180
181impl BackingPackageStore for CheckpointData {
182    fn get_package_object(
183        &self,
184        package_id: &crate::base_types::ObjectID,
185    ) -> crate::error::SuiResult<Option<crate::storage::PackageObject>> {
186        self.transactions
187            .iter()
188            .flat_map(|transaction| transaction.output_objects.iter())
189            .find(|object| object.is_package() && &object.id() == package_id)
190            .cloned()
191            .map(crate::storage::PackageObject::new)
192            .pipe(Ok)
193    }
194}
195
196// Never remove these asserts!
197// These data structures are meant to be used in-memory, for structures that can be persisted in
198// storage you should look at the protobuf versions.
199static_assertions::assert_not_impl_any!(Checkpoint: serde::Serialize, serde::de::DeserializeOwned);
200static_assertions::assert_not_impl_any!(ExecutedTransaction: serde::Serialize, serde::de::DeserializeOwned);
201static_assertions::assert_not_impl_any!(ObjectSet: serde::Serialize, serde::de::DeserializeOwned);
202
203#[derive(Clone, Debug)]
204pub struct Checkpoint {
205    pub summary: CertifiedCheckpointSummary,
206    pub contents: CheckpointContents,
207    pub transactions: Vec<ExecutedTransaction>,
208    pub object_set: ObjectSet,
209}
210
211#[derive(Clone, Debug)]
212pub struct ExecutedTransaction {
213    /// The input Transaction
214    pub transaction: TransactionData,
215    pub signatures: Vec<GenericSignature>,
216    /// The effects produced by executing this transaction
217    pub effects: TransactionEffects,
218    /// The events, if any, emitted by this transactions during execution
219    pub events: Option<TransactionEvents>,
220    pub unchanged_loaded_runtime_objects: Vec<ObjectKey>,
221}
222
223#[derive(Default, Clone, Debug)]
224pub struct ObjectSet(BTreeMap<ObjectKey, Object>);
225
226impl ObjectSet {
227    pub fn get(&self, key: &ObjectKey) -> Option<&Object> {
228        self.0.get(key)
229    }
230
231    pub fn insert(&mut self, object: Object) {
232        self.0
233            .insert(ObjectKey(object.id(), object.version()), object);
234    }
235
236    pub fn iter(&self) -> impl Iterator<Item = &Object> {
237        self.0.values()
238    }
239
240    pub fn len(&self) -> usize {
241        self.0.len()
242    }
243
244    pub fn is_empty(&self) -> bool {
245        self.0.is_empty()
246    }
247}
248
249impl Checkpoint {
250    pub fn latest_live_output_objects(&self) -> BTreeMap<ObjectID, Object> {
251        let mut latest_live_output_objects = BTreeMap::new();
252        for tx in self.transactions.iter() {
253            for obj in tx.output_objects(&self.object_set) {
254                latest_live_output_objects.insert(obj.id(), obj.clone());
255            }
256            for obj_ref in tx
257                .effects
258                .deleted()
259                .into_iter()
260                .chain(tx.effects.wrapped().into_iter())
261                .chain(tx.effects.unwrapped_then_deleted().into_iter())
262            {
263                latest_live_output_objects.remove(&obj_ref.0);
264            }
265        }
266        latest_live_output_objects
267    }
268
269    pub fn eventually_removed_object_refs_post_version(&self) -> Vec<ObjectRef> {
270        let mut eventually_removed_object_refs = BTreeMap::new();
271        for tx in self.transactions.iter() {
272            for obj_ref in tx
273                .effects
274                .deleted()
275                .into_iter()
276                .chain(tx.effects.wrapped().into_iter())
277                .chain(tx.effects.unwrapped_then_deleted().into_iter())
278            {
279                eventually_removed_object_refs.insert(obj_ref.0, obj_ref);
280            }
281            for obj in tx.output_objects(&self.object_set) {
282                eventually_removed_object_refs.remove(&obj.id());
283            }
284        }
285        eventually_removed_object_refs.into_values().collect()
286    }
287}
288
289impl ExecutedTransaction {
290    pub fn input_objects<'a>(
291        &self,
292        object_set: &'a ObjectSet,
293    ) -> impl Iterator<Item = &'a Object> + 'a {
294        self.effects
295            .object_changes()
296            .into_iter()
297            .filter_map(move |change| {
298                change
299                    .input_version
300                    .and_then(|version| object_set.get(&ObjectKey(change.id, version)))
301            })
302    }
303
304    pub fn output_objects<'a>(
305        &self,
306        object_set: &'a ObjectSet,
307    ) -> impl Iterator<Item = &'a Object> + 'a {
308        self.effects
309            .object_changes()
310            .into_iter()
311            .filter_map(move |change| {
312                change
313                    .output_version
314                    .and_then(|version| object_set.get(&ObjectKey(change.id, version)))
315            })
316    }
317}
318
319impl From<Checkpoint> for CheckpointData {
320    fn from(value: Checkpoint) -> Self {
321        let transactions = value
322            .transactions
323            .into_iter()
324            .map(|tx| {
325                let input_objects = tx
326                    .effects
327                    .modified_at_versions()
328                    .into_iter()
329                    .filter_map(|(object_id, version)| {
330                        value
331                            .object_set
332                            .get(&ObjectKey(object_id, version))
333                            .cloned()
334                    })
335                    .collect::<Vec<_>>();
336                let output_objects = tx
337                    .effects
338                    .all_changed_objects()
339                    .into_iter()
340                    .filter_map(|(object_ref, _owner, _kind)| {
341                        value.object_set.get(&object_ref.into()).cloned()
342                    })
343                    .collect::<Vec<_>>();
344
345                CheckpointTransaction {
346                    transaction: Transaction::from_generic_sig_data(tx.transaction, tx.signatures),
347                    effects: tx.effects,
348                    events: tx.events,
349                    input_objects,
350                    output_objects,
351                }
352            })
353            .collect();
354        Self {
355            checkpoint_summary: value.summary,
356            checkpoint_contents: value.contents,
357            transactions,
358        }
359    }
360}
361
362// Lossy conversion
363impl From<CheckpointData> for Checkpoint {
364    fn from(value: CheckpointData) -> Self {
365        let mut object_set = ObjectSet::default();
366        let transactions = value
367            .transactions
368            .into_iter()
369            .map(|tx| {
370                for o in tx
371                    .input_objects
372                    .into_iter()
373                    .chain(tx.output_objects.into_iter())
374                {
375                    object_set.insert(o);
376                }
377
378                let sender_signed = tx.transaction.into_data().into_inner();
379
380                ExecutedTransaction {
381                    transaction: sender_signed.intent_message.value,
382                    signatures: sender_signed.tx_signatures,
383                    effects: tx.effects,
384                    events: tx.events,
385
386                    // lossy
387                    unchanged_loaded_runtime_objects: Vec::new(),
388                }
389            })
390            .collect();
391        Self {
392            summary: value.checkpoint_summary,
393            contents: value.checkpoint_contents,
394            transactions,
395            object_set,
396        }
397    }
398}