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 epoch_info(&self) -> Result<Option<EpochInfo>, StorageError> {
251        if self.summary.end_of_epoch_data.is_none() && self.summary.sequence_number != 0 {
252            return Ok(None);
253        }
254
255        let (start_checkpoint, transaction) = if self.summary.sequence_number == 0 {
256            (0, &self.transactions[0])
257        } else {
258            let Some(transaction) = self.transactions.iter().find(|tx| {
259                matches!(
260                    tx.transaction.kind(),
261                    TransactionKind::ChangeEpoch(_) | TransactionKind::EndOfEpochTransaction(_)
262                )
263            }) else {
264                return Err(StorageError::custom(format!(
265                    "Failed to get end of epoch transaction in checkpoint {} with EndOfEpochData",
266                    self.summary.sequence_number,
267                )));
268            };
269            (self.summary.sequence_number + 1, transaction)
270        };
271
272        let output_objects: Vec<Object> = transaction
273            .output_objects(&self.object_set)
274            .cloned()
275            .collect();
276        let system_state = get_sui_system_state(&output_objects.as_slice()).map_err(|e| {
277            StorageError::custom(format!(
278                "Failed to find system state object output from end of epoch transaction: {e}"
279            ))
280        })?;
281
282        Ok(Some(EpochInfo {
283            epoch: system_state.epoch(),
284            protocol_version: Some(system_state.protocol_version()),
285            start_timestamp_ms: Some(system_state.epoch_start_timestamp_ms()),
286            end_timestamp_ms: None,
287            start_checkpoint: Some(start_checkpoint),
288            end_checkpoint: None,
289            reference_gas_price: Some(system_state.reference_gas_price()),
290            system_state: Some(system_state),
291        }))
292    }
293
294    pub fn latest_live_output_objects(&self) -> BTreeMap<ObjectID, Object> {
295        let mut latest_live_output_objects = BTreeMap::new();
296        for tx in self.transactions.iter() {
297            for obj in tx.output_objects(&self.object_set) {
298                latest_live_output_objects.insert(obj.id(), obj.clone());
299            }
300            for obj_ref in tx
301                .effects
302                .deleted()
303                .into_iter()
304                .chain(tx.effects.wrapped().into_iter())
305                .chain(tx.effects.unwrapped_then_deleted().into_iter())
306            {
307                latest_live_output_objects.remove(&obj_ref.0);
308            }
309        }
310        latest_live_output_objects
311    }
312
313    pub fn eventually_removed_object_refs_post_version(&self) -> Vec<ObjectRef> {
314        let mut eventually_removed_object_refs = BTreeMap::new();
315        for tx in self.transactions.iter() {
316            for obj_ref in tx
317                .effects
318                .deleted()
319                .into_iter()
320                .chain(tx.effects.wrapped().into_iter())
321                .chain(tx.effects.unwrapped_then_deleted().into_iter())
322            {
323                eventually_removed_object_refs.insert(obj_ref.0, obj_ref);
324            }
325            for obj in tx.output_objects(&self.object_set) {
326                eventually_removed_object_refs.remove(&obj.id());
327            }
328        }
329        eventually_removed_object_refs.into_values().collect()
330    }
331
332    // Returns the required FieldMask to fetch all necessary fields for populating `Checkpoint`
333    pub fn proto_field_mask() -> sui_rpc::field::FieldMask {
334        use sui_rpc::field::FieldMaskUtil;
335        use sui_rpc::proto::sui::rpc::v2::Checkpoint;
336
337        sui_rpc::field::FieldMask::from_paths([
338            Checkpoint::path_builder().sequence_number(),
339            Checkpoint::path_builder().summary().bcs().value(),
340            Checkpoint::path_builder().signature().finish(),
341            Checkpoint::path_builder().contents().bcs().value(),
342            Checkpoint::path_builder()
343                .transactions()
344                .transaction()
345                .bcs()
346                .value(),
347            Checkpoint::path_builder()
348                .transactions()
349                .effects()
350                .bcs()
351                .value(),
352            Checkpoint::path_builder()
353                .transactions()
354                .effects()
355                .unchanged_loaded_runtime_objects()
356                .finish(),
357            Checkpoint::path_builder()
358                .transactions()
359                .events()
360                .bcs()
361                .value(),
362            Checkpoint::path_builder().objects().objects().bcs().value(),
363        ])
364    }
365}
366
367impl ExecutedTransaction {
368    pub fn input_objects<'a>(
369        &self,
370        object_set: &'a ObjectSet,
371    ) -> impl Iterator<Item = &'a Object> + 'a {
372        self.effects
373            .object_changes()
374            .into_iter()
375            .filter_map(move |change| {
376                change
377                    .input_version
378                    .and_then(|version| object_set.get(&ObjectKey(change.id, version)))
379            })
380    }
381
382    pub fn output_objects<'a>(
383        &self,
384        object_set: &'a ObjectSet,
385    ) -> impl Iterator<Item = &'a Object> + 'a {
386        self.effects
387            .object_changes()
388            .into_iter()
389            .filter_map(move |change| {
390                change
391                    .output_version
392                    .and_then(|version| object_set.get(&ObjectKey(change.id, version)))
393            })
394    }
395
396    pub fn created_objects<'a>(
397        &self,
398        object_set: &'a ObjectSet,
399    ) -> impl Iterator<Item = &'a Object> + 'a {
400        self.effects
401            .created()
402            .into_iter()
403            .filter_map(move |((id, version, _), _)| object_set.get(&ObjectKey(id, version)))
404    }
405}
406
407impl From<Checkpoint> for CheckpointData {
408    fn from(value: Checkpoint) -> Self {
409        let transactions = value
410            .transactions
411            .into_iter()
412            .map(|tx| {
413                let input_objects = tx
414                    .effects
415                    .modified_at_versions()
416                    .into_iter()
417                    .filter_map(|(object_id, version)| {
418                        value
419                            .object_set
420                            .get(&ObjectKey(object_id, version))
421                            .cloned()
422                    })
423                    .collect::<Vec<_>>();
424                let output_objects = tx
425                    .effects
426                    .all_changed_objects()
427                    .into_iter()
428                    .filter_map(|(object_ref, _owner, _kind)| {
429                        value.object_set.get(&object_ref.into()).cloned()
430                    })
431                    .collect::<Vec<_>>();
432
433                CheckpointTransaction {
434                    transaction: Transaction::from_generic_sig_data(tx.transaction, tx.signatures),
435                    effects: tx.effects,
436                    events: tx.events,
437                    input_objects,
438                    output_objects,
439                }
440            })
441            .collect();
442        Self {
443            checkpoint_summary: value.summary,
444            checkpoint_contents: value.contents,
445            transactions,
446        }
447    }
448}
449
450// Lossy conversion
451impl From<CheckpointData> for Checkpoint {
452    fn from(value: CheckpointData) -> Self {
453        let mut object_set = ObjectSet::default();
454        let transactions = value
455            .transactions
456            .into_iter()
457            .map(|tx| {
458                for o in tx
459                    .input_objects
460                    .into_iter()
461                    .chain(tx.output_objects.into_iter())
462                {
463                    object_set.insert(o);
464                }
465
466                let sender_signed = tx.transaction.into_data().into_inner();
467
468                ExecutedTransaction {
469                    transaction: sender_signed.intent_message.value,
470                    signatures: sender_signed.tx_signatures,
471                    effects: tx.effects,
472                    events: tx.events,
473
474                    // lossy
475                    unchanged_loaded_runtime_objects: Vec::new(),
476                }
477            })
478            .collect();
479        Self {
480            summary: value.checkpoint_summary,
481            contents: value.checkpoint_contents,
482            transactions,
483            object_set,
484        }
485    }
486}