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    // Returns the required FieldMask to fetch all necessary fields for populating `Checkpoint`
289    pub fn proto_field_mask() -> sui_rpc::field::FieldMask {
290        use sui_rpc::field::FieldMaskUtil;
291        use sui_rpc::proto::sui::rpc::v2::Checkpoint;
292
293        sui_rpc::field::FieldMask::from_paths([
294            Checkpoint::path_builder().sequence_number(),
295            Checkpoint::path_builder().summary().bcs().value(),
296            Checkpoint::path_builder().signature().finish(),
297            Checkpoint::path_builder().contents().bcs().value(),
298            Checkpoint::path_builder()
299                .transactions()
300                .transaction()
301                .bcs()
302                .value(),
303            Checkpoint::path_builder()
304                .transactions()
305                .effects()
306                .bcs()
307                .value(),
308            Checkpoint::path_builder()
309                .transactions()
310                .effects()
311                .unchanged_loaded_runtime_objects()
312                .finish(),
313            Checkpoint::path_builder()
314                .transactions()
315                .events()
316                .bcs()
317                .value(),
318            Checkpoint::path_builder().objects().objects().bcs().value(),
319        ])
320    }
321}
322
323impl ExecutedTransaction {
324    pub fn input_objects<'a>(
325        &self,
326        object_set: &'a ObjectSet,
327    ) -> impl Iterator<Item = &'a Object> + 'a {
328        self.effects
329            .object_changes()
330            .into_iter()
331            .filter_map(move |change| {
332                change
333                    .input_version
334                    .and_then(|version| object_set.get(&ObjectKey(change.id, version)))
335            })
336    }
337
338    pub fn output_objects<'a>(
339        &self,
340        object_set: &'a ObjectSet,
341    ) -> impl Iterator<Item = &'a Object> + 'a {
342        self.effects
343            .object_changes()
344            .into_iter()
345            .filter_map(move |change| {
346                change
347                    .output_version
348                    .and_then(|version| object_set.get(&ObjectKey(change.id, version)))
349            })
350    }
351}
352
353impl From<Checkpoint> for CheckpointData {
354    fn from(value: Checkpoint) -> Self {
355        let transactions = value
356            .transactions
357            .into_iter()
358            .map(|tx| {
359                let input_objects = tx
360                    .effects
361                    .modified_at_versions()
362                    .into_iter()
363                    .filter_map(|(object_id, version)| {
364                        value
365                            .object_set
366                            .get(&ObjectKey(object_id, version))
367                            .cloned()
368                    })
369                    .collect::<Vec<_>>();
370                let output_objects = tx
371                    .effects
372                    .all_changed_objects()
373                    .into_iter()
374                    .filter_map(|(object_ref, _owner, _kind)| {
375                        value.object_set.get(&object_ref.into()).cloned()
376                    })
377                    .collect::<Vec<_>>();
378
379                CheckpointTransaction {
380                    transaction: Transaction::from_generic_sig_data(tx.transaction, tx.signatures),
381                    effects: tx.effects,
382                    events: tx.events,
383                    input_objects,
384                    output_objects,
385                }
386            })
387            .collect();
388        Self {
389            checkpoint_summary: value.summary,
390            checkpoint_contents: value.contents,
391            transactions,
392        }
393    }
394}
395
396// Lossy conversion
397impl From<CheckpointData> for Checkpoint {
398    fn from(value: CheckpointData) -> Self {
399        let mut object_set = ObjectSet::default();
400        let transactions = value
401            .transactions
402            .into_iter()
403            .map(|tx| {
404                for o in tx
405                    .input_objects
406                    .into_iter()
407                    .chain(tx.output_objects.into_iter())
408                {
409                    object_set.insert(o);
410                }
411
412                let sender_signed = tx.transaction.into_data().into_inner();
413
414                ExecutedTransaction {
415                    transaction: sender_signed.intent_message.value,
416                    signatures: sender_signed.tx_signatures,
417                    effects: tx.effects,
418                    events: tx.events,
419
420                    // lossy
421                    unchanged_loaded_runtime_objects: Vec::new(),
422                }
423            })
424            .collect();
425        Self {
426            summary: value.checkpoint_summary,
427            contents: value.checkpoint_contents,
428            transactions,
429            object_set,
430        }
431    }
432}