1use 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 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 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 pub transaction: Transaction,
109 pub effects: TransactionEffects,
111 pub events: Option<TransactionEvents>,
113 pub input_objects: Vec<Object>,
115 pub output_objects: Vec<Object>,
117}
118
119impl CheckpointTransaction {
120 pub fn removed_objects_pre_version(&self) -> impl Iterator<Item = &Object> {
122 self.effects
125 .all_removed_objects()
126 .into_iter() .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 self.effects
162 .created()
163 .into_iter()
164 .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
196static_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 pub transaction: TransactionData,
215 pub signatures: Vec<GenericSignature>,
216 pub effects: TransactionEffects,
218 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 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
450impl 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 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}