iota_types/
full_checkpoint_content.rs1use std::collections::BTreeMap;
6
7use iota_sdk_types::{ObjectId, TransactionKind};
8use serde::{Deserialize, Serialize};
9use tap::Pipe;
10
11use crate::{
12 base_types::ObjectRef,
13 effects::{
14 TransactionEffects, TransactionEffectsAPI, TransactionEffectsExt, TransactionEvents,
15 },
16 iota_system_state::{IotaSystemStateTrait, get_iota_system_state},
17 messages_checkpoint::{CertifiedCheckpointSummary, CheckpointContents},
18 object::Object,
19 storage::{BackingPackageStore, EpochInfo, error::Error as StorageError},
20 transaction::{Transaction, TransactionDataAPI},
21};
22
23#[derive(Clone, Debug, Serialize, Deserialize)]
24pub struct CheckpointData {
25 pub checkpoint_summary: CertifiedCheckpointSummary,
26 pub checkpoint_contents: CheckpointContents,
27 pub transactions: Vec<CheckpointTransaction>,
28}
29
30impl CheckpointData {
31 pub fn latest_live_output_objects(&self) -> Vec<&Object> {
34 let mut latest_live_objects = BTreeMap::new();
35 for tx in self.transactions.iter() {
36 for obj in tx.output_objects.iter() {
37 latest_live_objects.insert(obj.id(), obj);
38 }
39 for obj_ref in tx.removed_object_refs_post_version() {
40 latest_live_objects.remove(&obj_ref.object_id);
41 }
42 }
43 latest_live_objects.into_values().collect()
44 }
45
46 pub fn eventually_removed_object_refs_post_version(&self) -> Vec<ObjectRef> {
49 let mut eventually_removed_object_refs = BTreeMap::new();
50 for tx in self.transactions.iter() {
51 for obj_ref in tx.removed_object_refs_post_version() {
52 eventually_removed_object_refs.insert(obj_ref.object_id, obj_ref);
53 }
54 for obj in tx.output_objects.iter() {
55 eventually_removed_object_refs.remove(&(obj.id()));
56 }
57 }
58 eventually_removed_object_refs.into_values().collect()
59 }
60
61 pub fn all_objects(&self) -> Vec<&Object> {
62 self.transactions
63 .iter()
64 .flat_map(|tx| &tx.input_objects)
65 .chain(self.transactions.iter().flat_map(|tx| &tx.output_objects))
66 .collect()
67 }
68
69 pub fn epoch_info(&self) -> Result<Option<EpochInfo>, StorageError> {
70 if self.checkpoint_summary.end_of_epoch_data.is_none()
72 && self.checkpoint_summary.sequence_number != 0
73 {
74 return Ok(None);
75 }
76
77 let (start_checkpoint, transaction) = if self.checkpoint_summary.sequence_number != 0 {
78 let Some(transaction) = self.transactions.iter().find(|tx| {
79 matches!(
80 tx.transaction.intent_message().value.kind(),
81 TransactionKind::EndOfEpoch(_)
82 )
83 }) else {
84 return Err(StorageError::custom(format!(
85 "Failed to get end of epoch transaction in checkpoint {} with EndOfEpochData",
86 self.checkpoint_summary.sequence_number,
87 )));
88 };
89 (self.checkpoint_summary.sequence_number + 1, transaction)
90 } else {
91 let Some(transaction) = self.transactions.iter().find(|tx| {
93 matches!(
94 tx.transaction.intent_message().value.kind(),
95 TransactionKind::Genesis(_)
96 )
97 }) else {
98 return Err(StorageError::custom(format!(
99 "Failed to get genesis transaction in checkpoint {}",
100 self.checkpoint_summary.sequence_number,
101 )));
102 };
103 (0, transaction)
104 };
105
106 let system_state =
107 get_iota_system_state(&transaction.output_objects.as_slice()).map_err(|e| {
108 StorageError::custom(format!(
109 "Failed to find system state object output from end of epoch or genesis transaction: {e}"
110 ))
111 })?;
112
113 Ok(Some(EpochInfo {
114 epoch: system_state.epoch(),
115 protocol_version: system_state.protocol_version(),
116 start_timestamp_ms: system_state.epoch_start_timestamp_ms(),
117 end_timestamp_ms: None,
118 start_checkpoint,
119 end_checkpoint: None,
120 reference_gas_price: system_state.reference_gas_price(),
121 system_state,
122 }))
123 }
124}
125
126#[derive(Clone, Debug, Serialize, Deserialize)]
127pub struct CheckpointTransaction {
128 pub transaction: Transaction,
130 pub effects: TransactionEffects,
132 pub events: Option<TransactionEvents>,
134 pub input_objects: Vec<Object>,
137 pub output_objects: Vec<Object>,
140}
141
142impl CheckpointTransaction {
143 pub fn removed_objects_pre_version(&self) -> impl Iterator<Item = &Object> {
145 self.effects
149 .all_removed_objects()
150 .into_iter() .map(|(object_ref, _)| {
152 self.input_objects
153 .iter()
154 .find(|o| o.id() == object_ref.object_id)
155 .expect("all removed objects should show up in input objects")
156 })
157 }
158
159 pub fn removed_object_refs_post_version(&self) -> impl Iterator<Item = ObjectRef> {
160 let deleted = self.effects.deleted().into_iter();
161 let wrapped = self.effects.wrapped().into_iter();
162 let unwrapped_then_deleted = self.effects.unwrapped_then_deleted().into_iter();
163 deleted.chain(wrapped).chain(unwrapped_then_deleted)
164 }
165
166 pub fn changed_objects(&self) -> impl Iterator<Item = (&Object, Option<&Object>)> {
167 self.effects
168 .all_changed_objects()
169 .into_iter()
170 .map(|(object_ref, ..)| {
171 let object = self
172 .output_objects
173 .iter()
174 .find(|o| o.id() == object_ref.object_id)
175 .expect("changed objects should show up in output objects");
176
177 let old_object = self
178 .input_objects
179 .iter()
180 .find(|o| o.id() == object_ref.object_id);
181
182 (object, old_object)
183 })
184 }
185
186 pub fn created_objects(&self) -> impl Iterator<Item = &Object> {
187 self.effects
189 .created()
190 .into_iter()
191 .map(|(object_ref, _)| {
193 self.output_objects
194 .iter()
195 .find(|o| o.id() == object_ref.object_id && o.version() == object_ref.version)
196 .expect("created objects should show up in output objects")
197 })
198 }
199}
200
201impl BackingPackageStore for CheckpointData {
202 fn get_package_object(
203 &self,
204 package_id: &ObjectId,
205 ) -> crate::error::IotaResult<Option<crate::storage::PackageObject>> {
206 self.transactions
207 .iter()
208 .flat_map(|transaction| transaction.output_objects.iter())
209 .find(|object| object.is_package() && &object.id() == package_id)
210 .cloned()
211 .map(crate::storage::PackageObject::new)
212 .pipe(Ok)
213 }
214}