1use std::collections::{BTreeMap, HashSet};
6
7use serde::{Deserialize, Serialize};
8use tap::Pipe;
9
10use crate::{
11 base_types::{ObjectID, ObjectRef},
12 effects::{
13 IDOperation, ObjectIn, ObjectOut, TransactionEffects, TransactionEffectsAPI,
14 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, TransactionKind},
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.0));
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.0, 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 checkpoint_input_objects(&self) -> BTreeMap<ObjectID, &Object> {
64 let mut output_objects_seen = HashSet::new();
65 let mut checkpoint_input_objects = BTreeMap::new();
66 for tx in self.transactions.iter() {
67 for obj in tx.input_objects.iter() {
68 let id = obj.id();
69 if output_objects_seen.contains(&id) || checkpoint_input_objects.contains_key(&id) {
70 continue;
71 }
72 checkpoint_input_objects.insert(id, obj);
73 }
74 for obj in tx.output_objects.iter() {
75 output_objects_seen.insert(obj.id());
78 }
79 }
80 checkpoint_input_objects
81 }
82
83 pub fn all_objects(&self) -> Vec<&Object> {
84 self.transactions
85 .iter()
86 .flat_map(|tx| &tx.input_objects)
87 .chain(self.transactions.iter().flat_map(|tx| &tx.output_objects))
88 .collect()
89 }
90
91 pub fn epoch_info(&self) -> Result<Option<EpochInfo>, StorageError> {
92 if self.checkpoint_summary.end_of_epoch_data.is_none()
94 && self.checkpoint_summary.sequence_number != 0
95 {
96 return Ok(None);
97 }
98
99 let (start_checkpoint, transaction) = if self.checkpoint_summary.sequence_number != 0 {
100 let Some(transaction) = self.transactions.iter().find(|tx| {
101 matches!(
102 tx.transaction.intent_message().value.kind(),
103 TransactionKind::EndOfEpochTransaction(_)
104 )
105 }) else {
106 return Err(StorageError::custom(format!(
107 "Failed to get end of epoch transaction in checkpoint {} with EndOfEpochData",
108 self.checkpoint_summary.sequence_number,
109 )));
110 };
111 (self.checkpoint_summary.sequence_number + 1, transaction)
112 } else {
113 let Some(transaction) = self.transactions.iter().find(|tx| {
115 matches!(
116 tx.transaction.intent_message().value.kind(),
117 TransactionKind::Genesis(_)
118 )
119 }) else {
120 return Err(StorageError::custom(format!(
121 "Failed to get genesis transaction in checkpoint {}",
122 self.checkpoint_summary.sequence_number,
123 )));
124 };
125 (0, transaction)
126 };
127
128 let system_state =
129 get_iota_system_state(&transaction.output_objects.as_slice()).map_err(|e| {
130 StorageError::custom(format!(
131 "Failed to find system state object output from end of epoch or genesis transaction: {e}"
132 ))
133 })?;
134
135 Ok(Some(EpochInfo {
136 epoch: system_state.epoch(),
137 protocol_version: system_state.protocol_version(),
138 start_timestamp_ms: system_state.epoch_start_timestamp_ms(),
139 end_timestamp_ms: None,
140 start_checkpoint,
141 end_checkpoint: None,
142 reference_gas_price: system_state.reference_gas_price(),
143 system_state,
144 }))
145 }
146}
147
148#[derive(Clone, Debug, Serialize, Deserialize)]
149pub struct CheckpointTransaction {
150 pub transaction: Transaction,
152 pub effects: TransactionEffects,
154 pub events: Option<TransactionEvents>,
156 pub input_objects: Vec<Object>,
159 pub output_objects: Vec<Object>,
162}
163
164impl CheckpointTransaction {
165 pub fn removed_objects_pre_version(&self) -> impl Iterator<Item = &Object> {
167 match &self.effects {
169 TransactionEffects::V1(v1) => {
170 v1.changed_objects().iter().filter_map(|(id, change)| {
171 match (
172 &change.input_state,
173 &change.output_state,
174 &change.id_operation,
175 ) {
176 (
178 ObjectIn::Exist(((version, _d), _o)),
179 ObjectOut::NotExist,
180 IDOperation::Deleted,
181 ) => Some((id, version)),
182
183 (
185 ObjectIn::Exist(((version, _), _)),
186 ObjectOut::NotExist,
187 IDOperation::None,
188 ) => Some((id, version)),
189 _ => None,
190 }
191 })
192 }
193 }
194 .map(|(id, version)| {
196 self.input_objects
197 .iter()
198 .find(|o| &o.id() == id && &o.version() == version)
199 .expect("all removed objects should show up in input objects")
200 })
201 }
202
203 pub fn removed_object_refs_post_version(&self) -> impl Iterator<Item = ObjectRef> {
204 let deleted = self.effects.deleted().into_iter();
205 let wrapped = self.effects.wrapped().into_iter();
206 let unwrapped_then_deleted = self.effects.unwrapped_then_deleted().into_iter();
207 deleted.chain(wrapped).chain(unwrapped_then_deleted)
208 }
209
210 pub fn changed_objects(&self) -> impl Iterator<Item = (&Object, Option<&Object>)> {
211 match &self.effects {
213 TransactionEffects::V1(v1) => {
214 v1.changed_objects().iter().filter_map(|(id, change)| {
215 match (
216 &change.input_state,
217 &change.output_state,
218 &change.id_operation,
219 ) {
220 (ObjectIn::NotExist, ObjectOut::ObjectWrite(_), IDOperation::Created) => {
222 Some(((id, &v1.lamport_version), None))
223 }
224 (
225 ObjectIn::NotExist,
226 ObjectOut::PackageWrite((version, _)),
227 IDOperation::Created,
228 ) => Some(((id, version), None)),
229
230 (ObjectIn::NotExist, ObjectOut::ObjectWrite(_), IDOperation::None) => {
232 Some(((id, &v1.lamport_version), None))
233 }
234
235 (ObjectIn::Exist(((old_version, _), _)), ObjectOut::ObjectWrite(_), _) => {
237 Some(((id, &v1.lamport_version), Some(old_version)))
238 }
239 (
240 ObjectIn::Exist(((old_version, _), _)),
241 ObjectOut::PackageWrite((version, _)),
242 _,
243 ) => Some(((id, version), Some(old_version))),
244
245 _ => None,
246 }
247 })
248 }
249 }
250 .map(|((id, version), old_version)| {
252 let object = self
253 .output_objects
254 .iter()
255 .find(|o| &o.id() == id && &o.version() == version)
256 .expect("changed objects should show up in output objects");
257
258 let old_object = old_version.map(|old_version| {
259 self.input_objects
260 .iter()
261 .find(|o| &o.id() == id && &o.version() == old_version)
262 .expect("mutated objects should have a previous version in input objects")
263 });
264
265 (object, old_object)
266 })
267 }
268
269 pub fn created_objects(&self) -> impl Iterator<Item = &Object> {
270 match &self.effects {
272 TransactionEffects::V1(v1) => {
273 v1.changed_objects().iter().filter_map(|(id, change)| {
274 match (
275 &change.input_state,
276 &change.output_state,
277 &change.id_operation,
278 ) {
279 (ObjectIn::NotExist, ObjectOut::ObjectWrite(_), IDOperation::Created) => {
281 Some((id, &v1.lamport_version))
282 }
283 (
284 ObjectIn::NotExist,
285 ObjectOut::PackageWrite((version, _)),
286 IDOperation::Created,
287 ) => Some((id, version)),
288
289 _ => None,
290 }
291 })
292 }
293 }
294 .map(|(id, version)| {
296 self.output_objects
297 .iter()
298 .find(|o| &o.id() == id && &o.version() == version)
299 .expect("created objects should show up in output objects")
300 })
301 }
302}
303
304impl BackingPackageStore for CheckpointData {
305 fn get_package_object(
306 &self,
307 package_id: &crate::base_types::ObjectID,
308 ) -> crate::error::IotaResult<Option<crate::storage::PackageObject>> {
309 self.transactions
310 .iter()
311 .flat_map(|transaction| transaction.output_objects.iter())
312 .find(|object| object.is_package() && &object.id() == package_id)
313 .cloned()
314 .map(crate::storage::PackageObject::new)
315 .pipe(Ok)
316 }
317}