1#[cfg(debug_assertions)]
6use std::collections::HashSet;
7use std::collections::{BTreeMap, BTreeSet};
8
9use serde::{Deserialize, Serialize};
10
11use super::{
12 EffectsObjectChange, IDOperation, ObjectChange,
13 object_change::{ObjectIn, ObjectOut},
14};
15#[cfg(debug_assertions)]
16use crate::is_system_package;
17use crate::{
18 base_types::{
19 EpochId, IotaAddress, ObjectDigest, ObjectID, ObjectRef, SequenceNumber, TransactionDigest,
20 VersionDigest,
21 },
22 digests::{EffectsAuxDataDigest, TransactionEventsDigest},
23 effects::{InputSharedObject, TransactionEffectsAPI},
24 execution::SharedInput,
25 execution_status::ExecutionStatus,
26 gas::GasCostSummary,
27 object::{OBJECT_START_VERSION, Owner},
28};
29
30#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
32pub struct TransactionEffectsV1 {
33 pub(crate) status: ExecutionStatus,
35 pub(crate) executed_epoch: EpochId,
37 pub(crate) gas_used: GasCostSummary,
38 pub(crate) transaction_digest: TransactionDigest,
40 pub(crate) gas_object_index: Option<u32>,
44 pub(crate) events_digest: Option<TransactionEventsDigest>,
47 pub(crate) dependencies: Vec<TransactionDigest>,
49
50 pub(crate) lamport_version: SequenceNumber,
52 pub(crate) changed_objects: Vec<(ObjectID, EffectsObjectChange)>,
56 pub(crate) unchanged_shared_objects: Vec<(ObjectID, UnchangedSharedKind)>,
62 pub(crate) aux_data_digest: Option<EffectsAuxDataDigest>,
67}
68
69impl TransactionEffectsAPI for TransactionEffectsV1 {
70 fn status(&self) -> &ExecutionStatus {
71 &self.status
72 }
73
74 fn into_status(self) -> ExecutionStatus {
75 self.status
76 }
77
78 fn executed_epoch(&self) -> EpochId {
79 self.executed_epoch
80 }
81
82 fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)> {
83 self.changed_objects
84 .iter()
85 .filter_map(|(id, change)| {
86 if let ObjectIn::Exist(((version, _digest), _owner)) = &change.input_state {
87 Some((*id, *version))
88 } else {
89 None
90 }
91 })
92 .collect()
93 }
94
95 fn lamport_version(&self) -> SequenceNumber {
96 self.lamport_version
97 }
98
99 fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)> {
100 self.changed_objects
101 .iter()
102 .filter_map(|(id, change)| {
103 if let ObjectIn::Exist(((version, digest), owner)) = &change.input_state {
104 Some(((*id, *version, *digest), *owner))
105 } else {
106 None
107 }
108 })
109 .collect()
110 }
111
112 fn input_shared_objects(&self) -> Vec<InputSharedObject> {
113 self.changed_objects
114 .iter()
115 .filter_map(|(id, change)| match &change.input_state {
116 ObjectIn::Exist(((version, digest), Owner::Shared { .. })) => {
117 Some(InputSharedObject::Mutate((*id, *version, *digest)))
118 }
119 _ => None,
120 })
121 .chain(
122 self.unchanged_shared_objects
123 .iter()
124 .filter_map(|(id, change_kind)| match change_kind {
125 UnchangedSharedKind::ReadOnlyRoot((version, digest)) => {
126 Some(InputSharedObject::ReadOnly((*id, *version, *digest)))
127 }
128 UnchangedSharedKind::MutateDeleted(seqno) => {
129 Some(InputSharedObject::MutateDeleted(*id, *seqno))
130 }
131 UnchangedSharedKind::ReadDeleted(seqno) => {
132 Some(InputSharedObject::ReadDeleted(*id, *seqno))
133 }
134 UnchangedSharedKind::Cancelled(seqno) => {
135 Some(InputSharedObject::Cancelled(*id, *seqno))
136 }
137 UnchangedSharedKind::PerEpochConfig => None,
141 }),
142 )
143 .collect()
144 }
145
146 fn created(&self) -> Vec<(ObjectRef, Owner)> {
147 self.changed_objects
148 .iter()
149 .filter_map(|(id, change)| {
150 match (
151 &change.input_state,
152 &change.output_state,
153 &change.id_operation,
154 ) {
155 (
156 ObjectIn::NotExist,
157 ObjectOut::ObjectWrite((digest, owner)),
158 IDOperation::Created,
159 ) => Some(((*id, self.lamport_version, *digest), *owner)),
160 (
161 ObjectIn::NotExist,
162 ObjectOut::PackageWrite((version, digest)),
163 IDOperation::Created,
164 ) => Some(((*id, *version, *digest), Owner::Immutable)),
165 _ => None,
166 }
167 })
168 .collect()
169 }
170
171 fn mutated(&self) -> Vec<(ObjectRef, Owner)> {
172 self.changed_objects
173 .iter()
174 .filter_map(
175 |(id, change)| match (&change.input_state, &change.output_state) {
176 (ObjectIn::Exist(_), ObjectOut::ObjectWrite((digest, owner))) => {
177 Some(((*id, self.lamport_version, *digest), *owner))
178 }
179 (ObjectIn::Exist(_), ObjectOut::PackageWrite((version, digest))) => {
180 Some(((*id, *version, *digest), Owner::Immutable))
181 }
182 _ => None,
183 },
184 )
185 .collect()
186 }
187
188 fn unwrapped(&self) -> Vec<(ObjectRef, Owner)> {
189 self.changed_objects
190 .iter()
191 .filter_map(|(id, change)| {
192 match (
193 &change.input_state,
194 &change.output_state,
195 &change.id_operation,
196 ) {
197 (
198 ObjectIn::NotExist,
199 ObjectOut::ObjectWrite((digest, owner)),
200 IDOperation::None,
201 ) => Some(((*id, self.lamport_version, *digest), *owner)),
202 _ => None,
203 }
204 })
205 .collect()
206 }
207
208 fn deleted(&self) -> Vec<ObjectRef> {
209 self.changed_objects
210 .iter()
211 .filter_map(|(id, change)| {
212 match (
213 &change.input_state,
214 &change.output_state,
215 &change.id_operation,
216 ) {
217 (ObjectIn::Exist(_), ObjectOut::NotExist, IDOperation::Deleted) => Some((
218 *id,
219 self.lamport_version,
220 ObjectDigest::OBJECT_DIGEST_DELETED,
221 )),
222 _ => None,
223 }
224 })
225 .collect()
226 }
227
228 fn unwrapped_then_deleted(&self) -> Vec<ObjectRef> {
229 self.changed_objects
230 .iter()
231 .filter_map(|(id, change)| {
232 match (
233 &change.input_state,
234 &change.output_state,
235 &change.id_operation,
236 ) {
237 (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => Some((
238 *id,
239 self.lamport_version,
240 ObjectDigest::OBJECT_DIGEST_DELETED,
241 )),
242 _ => None,
243 }
244 })
245 .collect()
246 }
247
248 fn wrapped(&self) -> Vec<ObjectRef> {
249 self.changed_objects
250 .iter()
251 .filter_map(|(id, change)| {
252 match (
253 &change.input_state,
254 &change.output_state,
255 &change.id_operation,
256 ) {
257 (ObjectIn::Exist(_), ObjectOut::NotExist, IDOperation::None) => Some((
258 *id,
259 self.lamport_version,
260 ObjectDigest::OBJECT_DIGEST_WRAPPED,
261 )),
262 _ => None,
263 }
264 })
265 .collect()
266 }
267
268 fn object_changes(&self) -> Vec<ObjectChange> {
269 self.changed_objects
270 .iter()
271 .map(|(id, change)| {
272 let input_version_digest = match &change.input_state {
273 ObjectIn::NotExist => None,
274 ObjectIn::Exist((vd, _)) => Some(*vd),
275 };
276
277 let output_version_digest = match &change.output_state {
278 ObjectOut::NotExist => None,
279 ObjectOut::ObjectWrite((d, _)) => Some((self.lamport_version, *d)),
280 ObjectOut::PackageWrite(vd) => Some(*vd),
281 };
282
283 ObjectChange {
284 id: *id,
285
286 input_version: input_version_digest.map(|k| k.0),
287 input_digest: input_version_digest.map(|k| k.1),
288
289 output_version: output_version_digest.map(|k| k.0),
290 output_digest: output_version_digest.map(|k| k.1),
291
292 id_operation: change.id_operation,
293 }
294 })
295 .collect()
296 }
297
298 fn gas_object(&self) -> (ObjectRef, Owner) {
299 if let Some(gas_object_index) = self.gas_object_index {
300 let entry = &self.changed_objects[gas_object_index as usize];
301 match entry.1.output_state {
302 ObjectOut::ObjectWrite((digest, owner)) => {
303 ((entry.0, self.lamport_version, digest), owner)
304 }
305 _ => panic!("Gas object must be an ObjectWrite in changed_objects"),
306 }
307 } else {
308 (
309 (ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN),
310 Owner::AddressOwner(IotaAddress::default()),
311 )
312 }
313 }
314
315 fn events_digest(&self) -> Option<&TransactionEventsDigest> {
316 self.events_digest.as_ref()
317 }
318
319 fn dependencies(&self) -> &[TransactionDigest] {
320 &self.dependencies
321 }
322
323 fn transaction_digest(&self) -> &TransactionDigest {
324 &self.transaction_digest
325 }
326
327 fn gas_cost_summary(&self) -> &GasCostSummary {
328 &self.gas_used
329 }
330
331 fn unchanged_shared_objects(&self) -> Vec<(ObjectID, UnchangedSharedKind)> {
332 self.unchanged_shared_objects.clone()
333 }
334
335 fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus {
336 &mut self.status
337 }
338
339 fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary {
340 &mut self.gas_used
341 }
342
343 fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest {
344 &mut self.transaction_digest
345 }
346
347 fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest> {
348 &mut self.dependencies
349 }
350
351 fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject) {
352 match kind {
353 InputSharedObject::Mutate(obj_ref) => self.changed_objects.push((
354 obj_ref.0,
355 EffectsObjectChange {
356 input_state: ObjectIn::Exist((
357 (obj_ref.1, obj_ref.2),
358 Owner::Shared {
359 initial_shared_version: OBJECT_START_VERSION,
360 },
361 )),
362 output_state: ObjectOut::ObjectWrite((
363 obj_ref.2,
364 Owner::Shared {
365 initial_shared_version: obj_ref.1,
366 },
367 )),
368 id_operation: IDOperation::None,
369 },
370 )),
371 InputSharedObject::ReadOnly(obj_ref) => self.unchanged_shared_objects.push((
372 obj_ref.0,
373 UnchangedSharedKind::ReadOnlyRoot((obj_ref.1, obj_ref.2)),
374 )),
375 InputSharedObject::ReadDeleted(obj_id, seqno) => self
376 .unchanged_shared_objects
377 .push((obj_id, UnchangedSharedKind::ReadDeleted(seqno))),
378 InputSharedObject::MutateDeleted(obj_id, seqno) => self
379 .unchanged_shared_objects
380 .push((obj_id, UnchangedSharedKind::MutateDeleted(seqno))),
381 InputSharedObject::Cancelled(obj_id, seqno) => self
382 .unchanged_shared_objects
383 .push((obj_id, UnchangedSharedKind::Cancelled(seqno))),
384 }
385 }
386
387 fn unsafe_add_deleted_live_object_for_testing(&mut self, obj_ref: ObjectRef) {
388 self.changed_objects.push((
389 obj_ref.0,
390 EffectsObjectChange {
391 input_state: ObjectIn::Exist((
392 (obj_ref.1, obj_ref.2),
393 Owner::AddressOwner(IotaAddress::default()),
394 )),
395 output_state: ObjectOut::ObjectWrite((
396 obj_ref.2,
397 Owner::AddressOwner(IotaAddress::default()),
398 )),
399 id_operation: IDOperation::None,
400 },
401 ))
402 }
403
404 fn unsafe_add_object_tombstone_for_testing(&mut self, obj_ref: ObjectRef) {
405 self.changed_objects.push((
406 obj_ref.0,
407 EffectsObjectChange {
408 input_state: ObjectIn::Exist((
409 (obj_ref.1, obj_ref.2),
410 Owner::AddressOwner(IotaAddress::default()),
411 )),
412 output_state: ObjectOut::NotExist,
413 id_operation: IDOperation::Deleted,
414 },
415 ))
416 }
417}
418
419impl TransactionEffectsV1 {
420 pub fn new(
421 status: ExecutionStatus,
422 executed_epoch: EpochId,
423 gas_used: GasCostSummary,
424 shared_objects: Vec<SharedInput>,
425 loaded_per_epoch_config_objects: BTreeSet<ObjectID>,
426 transaction_digest: TransactionDigest,
427 lamport_version: SequenceNumber,
428 changed_objects: BTreeMap<ObjectID, EffectsObjectChange>,
429 gas_object: Option<ObjectID>,
430 events_digest: Option<TransactionEventsDigest>,
431 dependencies: Vec<TransactionDigest>,
432 ) -> Self {
433 let unchanged_shared_objects = shared_objects
434 .into_iter()
435 .filter_map(|shared_input| match shared_input {
436 SharedInput::Existing((id, version, digest)) => {
437 if changed_objects.contains_key(&id) {
438 None
439 } else {
440 Some((id, UnchangedSharedKind::ReadOnlyRoot((version, digest))))
441 }
442 }
443 SharedInput::Deleted((id, version, mutable, _)) => {
444 debug_assert!(!changed_objects.contains_key(&id));
445 if mutable {
446 Some((id, UnchangedSharedKind::MutateDeleted(version)))
447 } else {
448 Some((id, UnchangedSharedKind::ReadDeleted(version)))
449 }
450 }
451 SharedInput::Cancelled((id, version)) => {
452 debug_assert!(!changed_objects.contains_key(&id));
453 Some((id, UnchangedSharedKind::Cancelled(version)))
454 }
455 })
456 .chain(
457 loaded_per_epoch_config_objects
458 .into_iter()
459 .map(|id| (id, UnchangedSharedKind::PerEpochConfig)),
460 )
461 .collect();
462 let changed_objects: Vec<_> = changed_objects.into_iter().collect();
463
464 let gas_object_index = gas_object.map(|gas_id| {
465 changed_objects
466 .iter()
467 .position(|(id, _)| id == &gas_id)
468 .unwrap() as u32
469 });
470
471 let result = Self {
472 status,
473 executed_epoch,
474 gas_used,
475 transaction_digest,
476 lamport_version,
477 changed_objects,
478 unchanged_shared_objects,
479 gas_object_index,
480 events_digest,
481 dependencies,
482 aux_data_digest: None,
483 };
484 #[cfg(debug_assertions)]
485 result.check_invariant();
486
487 result
488 }
489
490 #[cfg(debug_assertions)]
494 fn check_invariant(&self) {
495 let mut unique_ids = HashSet::new();
496 for (id, change) in &self.changed_objects {
497 assert!(unique_ids.insert(*id));
498 match (
499 &change.input_state,
500 &change.output_state,
501 &change.id_operation,
502 ) {
503 (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Created) => {
504 }
506 (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => {
507 }
509 (ObjectIn::NotExist, ObjectOut::ObjectWrite((_, owner)), IDOperation::None) => {
510 assert!(!owner.is_shared());
513 }
514 (ObjectIn::NotExist, ObjectOut::ObjectWrite(..), IDOperation::Created) => {
515 }
517 (ObjectIn::NotExist, ObjectOut::PackageWrite(_), IDOperation::Created) => {
518 }
520 (
521 ObjectIn::Exist(((old_version, _), old_owner)),
522 ObjectOut::NotExist,
523 IDOperation::None,
524 ) => {
525 assert!(old_version.value() < self.lamport_version.value());
527 assert!(
528 !old_owner.is_shared() && !old_owner.is_immutable(),
529 "Cannot wrap shared or immutable object"
530 );
531 }
532 (
533 ObjectIn::Exist(((old_version, _), old_owner)),
534 ObjectOut::NotExist,
535 IDOperation::Deleted,
536 ) => {
537 assert!(old_version.value() < self.lamport_version.value());
539 assert!(!old_owner.is_immutable(), "Cannot delete immutable object");
540 }
541 (
542 ObjectIn::Exist(((old_version, old_digest), old_owner)),
543 ObjectOut::ObjectWrite((new_digest, new_owner)),
544 IDOperation::None,
545 ) => {
546 assert!(old_version.value() < self.lamport_version.value());
548 assert_ne!(old_digest, new_digest);
549 assert!(!old_owner.is_immutable(), "Cannot mutate immutable object");
550 if old_owner.is_shared() {
551 assert!(new_owner.is_shared(), "Cannot un-share an object");
552 } else {
553 assert!(!new_owner.is_shared(), "Cannot share an existing object");
554 }
555 }
556 (
557 ObjectIn::Exist(((old_version, old_digest), old_owner)),
558 ObjectOut::PackageWrite((new_version, new_digest)),
559 IDOperation::None,
560 ) => {
561 assert!(
563 old_owner.is_immutable() && is_system_package(*id),
564 "Must be a system package"
565 );
566 assert_eq!(old_version.value() + 1, new_version.value());
567 assert_ne!(old_digest, new_digest);
568 }
569 _ => {
570 panic!("Impossible object change: {id:?}, {change:?}");
571 }
572 }
573 }
574 let (_, owner) = self.gas_object();
576 assert!(matches!(owner, Owner::AddressOwner(_)));
577
578 for (id, _) in &self.unchanged_shared_objects {
579 assert!(
580 unique_ids.insert(*id),
581 "Duplicate object id: {id:?}\n{self:#?}"
582 );
583 }
584 }
585
586 pub fn aux_data_digest(&self) -> Option<EffectsAuxDataDigest> {
587 self.aux_data_digest
588 }
589}
590
591impl Default for TransactionEffectsV1 {
592 fn default() -> Self {
593 Self {
594 status: ExecutionStatus::Success,
595 executed_epoch: 0,
596 gas_used: GasCostSummary::default(),
597 transaction_digest: TransactionDigest::default(),
598 lamport_version: SequenceNumber::default(),
599 changed_objects: vec![],
600 unchanged_shared_objects: vec![],
601 gas_object_index: None,
602 events_digest: None,
603 dependencies: vec![],
604 aux_data_digest: None,
605 }
606 }
607}
608
609#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
610pub enum UnchangedSharedKind {
611 ReadOnlyRoot(VersionDigest),
615 MutateDeleted(SequenceNumber),
617 ReadDeleted(SequenceNumber),
619 Cancelled(SequenceNumber),
622 PerEpochConfig,
625}