iota_types/effects/
effects_v1.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5#[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/// The response from processing a transaction or a certified transaction
31#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
32pub struct TransactionEffectsV1 {
33    /// The status of the execution
34    pub(crate) status: ExecutionStatus,
35    /// The epoch when this transaction was executed.
36    pub(crate) executed_epoch: EpochId,
37    pub(crate) gas_used: GasCostSummary,
38    /// The transaction digest
39    pub(crate) transaction_digest: TransactionDigest,
40    /// The updated gas object reference, as an index into the `changed_objects`
41    /// vector. Having a dedicated field for convenient access.
42    /// System transaction that don't require gas will leave this as None.
43    pub(crate) gas_object_index: Option<u32>,
44    /// The digest of the events emitted during execution,
45    /// can be None if the transaction does not emit any event.
46    pub(crate) events_digest: Option<TransactionEventsDigest>,
47    /// The set of transaction digests this transaction depends on.
48    pub(crate) dependencies: Vec<TransactionDigest>,
49
50    /// The version number of all the written Move objects by this transaction.
51    pub(crate) lamport_version: SequenceNumber,
52    /// Objects whose state are changed in the object store.
53    /// This field should not be exposed to the public API.
54    /// Otherwise it will make it harder to use effects of different versions.
55    pub(crate) changed_objects: Vec<(ObjectID, EffectsObjectChange)>,
56    /// Shared objects that are not mutated in this transaction. Unlike owned
57    /// objects, read-only shared objects' version are not committed in the
58    /// transaction, and in order for a node to catch up and execute it
59    /// without consensus sequencing, the version needs to be committed in
60    /// the effects.
61    pub(crate) unchanged_shared_objects: Vec<(ObjectID, UnchangedSharedKind)>,
62    /// Auxiliary data that are not protocol-critical, generated as part of the
63    /// effects but are stored separately. Storing it separately allows us
64    /// to avoid bloating the effects with data that are not critical.
65    /// It also provides more flexibility on the format and type of the data.
66    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                        // We can not expose the per epoch config object as input shared object,
138                        // since it does not require sequencing, and hence shall not be considered
139                        // as a normal input shared object.
140                        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    /// This function demonstrates what's the invariant of the effects.
491    /// It also documents the semantics of different combinations in object
492    /// changes.
493    #[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                    // created and then wrapped Move object.
505                }
506                (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => {
507                    // unwrapped and then deleted Move object.
508                }
509                (ObjectIn::NotExist, ObjectOut::ObjectWrite((_, owner)), IDOperation::None) => {
510                    // unwrapped Move object.
511                    // It's not allowed to make an object shared after unwrapping.
512                    assert!(!owner.is_shared());
513                }
514                (ObjectIn::NotExist, ObjectOut::ObjectWrite(..), IDOperation::Created) => {
515                    // created Move object.
516                }
517                (ObjectIn::NotExist, ObjectOut::PackageWrite(_), IDOperation::Created) => {
518                    // created Move package or user Move package upgrade.
519                }
520                (
521                    ObjectIn::Exist(((old_version, _), old_owner)),
522                    ObjectOut::NotExist,
523                    IDOperation::None,
524                ) => {
525                    // wrapped.
526                    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                    // deleted.
538                    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                    // mutated.
547                    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                    // system package upgrade.
562                    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        // Make sure that gas object exists in changed_objects.
575        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    /// Read-only shared objects from the input. We don't really need
612    /// ObjectDigest for protocol correctness, but it will make it easier to
613    /// verify untrusted read.
614    ReadOnlyRoot(VersionDigest),
615    /// Deleted shared objects that appear mutably/owned in the input.
616    MutateDeleted(SequenceNumber),
617    /// Deleted shared objects that appear as read-only in the input.
618    ReadDeleted(SequenceNumber),
619    /// Shared objects in cancelled transaction. The sequence number embed
620    /// cancellation reason.
621    Cancelled(SequenceNumber),
622    /// Read of a per-epoch config object that should remain the same during an
623    /// epoch.
624    PerEpochConfig,
625}