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    pub(crate) changed_objects: Vec<(ObjectID, EffectsObjectChange)>,
54    /// Shared objects that are not mutated in this transaction. Unlike owned
55    /// objects, read-only shared objects' version are not committed in the
56    /// transaction, and in order for a node to catch up and execute it
57    /// without consensus sequencing, the version needs to be committed in
58    /// the effects.
59    pub(crate) unchanged_shared_objects: Vec<(ObjectID, UnchangedSharedKind)>,
60    /// Auxiliary data that are not protocol-critical, generated as part of the
61    /// effects but are stored separately. Storing it separately allows us
62    /// to avoid bloating the effects with data that are not critical.
63    /// It also provides more flexibility on the format and type of the data.
64    pub(crate) aux_data_digest: Option<EffectsAuxDataDigest>,
65}
66
67impl TransactionEffectsAPI for TransactionEffectsV1 {
68    fn status(&self) -> &ExecutionStatus {
69        &self.status
70    }
71
72    fn into_status(self) -> ExecutionStatus {
73        self.status
74    }
75
76    fn executed_epoch(&self) -> EpochId {
77        self.executed_epoch
78    }
79
80    fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)> {
81        self.changed_objects
82            .iter()
83            .filter_map(|(id, change)| {
84                if let ObjectIn::Exist(((version, _digest), _owner)) = &change.input_state {
85                    Some((*id, *version))
86                } else {
87                    None
88                }
89            })
90            .collect()
91    }
92
93    fn lamport_version(&self) -> SequenceNumber {
94        self.lamport_version
95    }
96
97    fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)> {
98        self.changed_objects
99            .iter()
100            .filter_map(|(id, change)| {
101                if let ObjectIn::Exist(((version, digest), owner)) = &change.input_state {
102                    Some(((*id, *version, *digest), *owner))
103                } else {
104                    None
105                }
106            })
107            .collect()
108    }
109
110    fn input_shared_objects(&self) -> Vec<InputSharedObject> {
111        self.changed_objects
112            .iter()
113            .filter_map(|(id, change)| match &change.input_state {
114                ObjectIn::Exist(((version, digest), Owner::Shared { .. })) => {
115                    Some(InputSharedObject::Mutate((*id, *version, *digest)))
116                }
117                _ => None,
118            })
119            .chain(
120                self.unchanged_shared_objects
121                    .iter()
122                    .filter_map(|(id, change_kind)| match change_kind {
123                        UnchangedSharedKind::ReadOnlyRoot((version, digest)) => {
124                            Some(InputSharedObject::ReadOnly((*id, *version, *digest)))
125                        }
126                        UnchangedSharedKind::MutateDeleted(seqno) => {
127                            Some(InputSharedObject::MutateDeleted(*id, *seqno))
128                        }
129                        UnchangedSharedKind::ReadDeleted(seqno) => {
130                            Some(InputSharedObject::ReadDeleted(*id, *seqno))
131                        }
132                        UnchangedSharedKind::Cancelled(seqno) => {
133                            Some(InputSharedObject::Cancelled(*id, *seqno))
134                        }
135                        // We can not expose the per epoch config object as input shared object,
136                        // since it does not require sequencing, and hence shall not be considered
137                        // as a normal input shared object.
138                        UnchangedSharedKind::PerEpochConfig => None,
139                    }),
140            )
141            .collect()
142    }
143
144    fn created(&self) -> Vec<(ObjectRef, Owner)> {
145        self.changed_objects
146            .iter()
147            .filter_map(|(id, change)| {
148                match (
149                    &change.input_state,
150                    &change.output_state,
151                    &change.id_operation,
152                ) {
153                    (
154                        ObjectIn::NotExist,
155                        ObjectOut::ObjectWrite((digest, owner)),
156                        IDOperation::Created,
157                    ) => Some(((*id, self.lamport_version, *digest), *owner)),
158                    (
159                        ObjectIn::NotExist,
160                        ObjectOut::PackageWrite((version, digest)),
161                        IDOperation::Created,
162                    ) => Some(((*id, *version, *digest), Owner::Immutable)),
163                    _ => None,
164                }
165            })
166            .collect()
167    }
168
169    fn mutated(&self) -> Vec<(ObjectRef, Owner)> {
170        self.changed_objects
171            .iter()
172            .filter_map(
173                |(id, change)| match (&change.input_state, &change.output_state) {
174                    (ObjectIn::Exist(_), ObjectOut::ObjectWrite((digest, owner))) => {
175                        Some(((*id, self.lamport_version, *digest), *owner))
176                    }
177                    (ObjectIn::Exist(_), ObjectOut::PackageWrite((version, digest))) => {
178                        Some(((*id, *version, *digest), Owner::Immutable))
179                    }
180                    _ => None,
181                },
182            )
183            .collect()
184    }
185
186    fn unwrapped(&self) -> Vec<(ObjectRef, Owner)> {
187        self.changed_objects
188            .iter()
189            .filter_map(|(id, change)| {
190                match (
191                    &change.input_state,
192                    &change.output_state,
193                    &change.id_operation,
194                ) {
195                    (
196                        ObjectIn::NotExist,
197                        ObjectOut::ObjectWrite((digest, owner)),
198                        IDOperation::None,
199                    ) => Some(((*id, self.lamport_version, *digest), *owner)),
200                    _ => None,
201                }
202            })
203            .collect()
204    }
205
206    fn deleted(&self) -> Vec<ObjectRef> {
207        self.changed_objects
208            .iter()
209            .filter_map(|(id, change)| {
210                match (
211                    &change.input_state,
212                    &change.output_state,
213                    &change.id_operation,
214                ) {
215                    (ObjectIn::Exist(_), ObjectOut::NotExist, IDOperation::Deleted) => Some((
216                        *id,
217                        self.lamport_version,
218                        ObjectDigest::OBJECT_DIGEST_DELETED,
219                    )),
220                    _ => None,
221                }
222            })
223            .collect()
224    }
225
226    fn unwrapped_then_deleted(&self) -> Vec<ObjectRef> {
227        self.changed_objects
228            .iter()
229            .filter_map(|(id, change)| {
230                match (
231                    &change.input_state,
232                    &change.output_state,
233                    &change.id_operation,
234                ) {
235                    (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => Some((
236                        *id,
237                        self.lamport_version,
238                        ObjectDigest::OBJECT_DIGEST_DELETED,
239                    )),
240                    _ => None,
241                }
242            })
243            .collect()
244    }
245
246    fn wrapped(&self) -> Vec<ObjectRef> {
247        self.changed_objects
248            .iter()
249            .filter_map(|(id, change)| {
250                match (
251                    &change.input_state,
252                    &change.output_state,
253                    &change.id_operation,
254                ) {
255                    (ObjectIn::Exist(_), ObjectOut::NotExist, IDOperation::None) => Some((
256                        *id,
257                        self.lamport_version,
258                        ObjectDigest::OBJECT_DIGEST_WRAPPED,
259                    )),
260                    _ => None,
261                }
262            })
263            .collect()
264    }
265
266    fn object_changes(&self) -> Vec<ObjectChange> {
267        self.changed_objects
268            .iter()
269            .map(|(id, change)| {
270                let input_version_digest = match &change.input_state {
271                    ObjectIn::NotExist => None,
272                    ObjectIn::Exist((vd, _)) => Some(*vd),
273                };
274
275                let output_version_digest = match &change.output_state {
276                    ObjectOut::NotExist => None,
277                    ObjectOut::ObjectWrite((d, _)) => Some((self.lamport_version, *d)),
278                    ObjectOut::PackageWrite(vd) => Some(*vd),
279                };
280
281                ObjectChange {
282                    id: *id,
283
284                    input_version: input_version_digest.map(|k| k.0),
285                    input_digest: input_version_digest.map(|k| k.1),
286
287                    output_version: output_version_digest.map(|k| k.0),
288                    output_digest: output_version_digest.map(|k| k.1),
289
290                    id_operation: change.id_operation,
291                }
292            })
293            .collect()
294    }
295
296    fn gas_object(&self) -> (ObjectRef, Owner) {
297        if let Some(gas_object_index) = self.gas_object_index {
298            let entry = &self.changed_objects[gas_object_index as usize];
299            match entry.1.output_state {
300                ObjectOut::ObjectWrite((digest, owner)) => {
301                    ((entry.0, self.lamport_version, digest), owner)
302                }
303                _ => panic!("Gas object must be an ObjectWrite in changed_objects"),
304            }
305        } else {
306            (
307                (ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN),
308                Owner::AddressOwner(IotaAddress::default()),
309            )
310        }
311    }
312
313    fn events_digest(&self) -> Option<&TransactionEventsDigest> {
314        self.events_digest.as_ref()
315    }
316
317    fn dependencies(&self) -> &[TransactionDigest] {
318        &self.dependencies
319    }
320
321    fn transaction_digest(&self) -> &TransactionDigest {
322        &self.transaction_digest
323    }
324
325    fn gas_cost_summary(&self) -> &GasCostSummary {
326        &self.gas_used
327    }
328
329    fn unchanged_shared_objects(&self) -> Vec<(ObjectID, UnchangedSharedKind)> {
330        self.unchanged_shared_objects.clone()
331    }
332
333    fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus {
334        &mut self.status
335    }
336
337    fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary {
338        &mut self.gas_used
339    }
340
341    fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest {
342        &mut self.transaction_digest
343    }
344
345    fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest> {
346        &mut self.dependencies
347    }
348
349    fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject) {
350        match kind {
351            InputSharedObject::Mutate(obj_ref) => self.changed_objects.push((
352                obj_ref.0,
353                EffectsObjectChange {
354                    input_state: ObjectIn::Exist((
355                        (obj_ref.1, obj_ref.2),
356                        Owner::Shared {
357                            initial_shared_version: OBJECT_START_VERSION,
358                        },
359                    )),
360                    output_state: ObjectOut::ObjectWrite((
361                        obj_ref.2,
362                        Owner::Shared {
363                            initial_shared_version: obj_ref.1,
364                        },
365                    )),
366                    id_operation: IDOperation::None,
367                },
368            )),
369            InputSharedObject::ReadOnly(obj_ref) => self.unchanged_shared_objects.push((
370                obj_ref.0,
371                UnchangedSharedKind::ReadOnlyRoot((obj_ref.1, obj_ref.2)),
372            )),
373            InputSharedObject::ReadDeleted(obj_id, seqno) => self
374                .unchanged_shared_objects
375                .push((obj_id, UnchangedSharedKind::ReadDeleted(seqno))),
376            InputSharedObject::MutateDeleted(obj_id, seqno) => self
377                .unchanged_shared_objects
378                .push((obj_id, UnchangedSharedKind::MutateDeleted(seqno))),
379            InputSharedObject::Cancelled(obj_id, seqno) => self
380                .unchanged_shared_objects
381                .push((obj_id, UnchangedSharedKind::Cancelled(seqno))),
382        }
383    }
384
385    fn unsafe_add_deleted_live_object_for_testing(&mut self, obj_ref: ObjectRef) {
386        self.changed_objects.push((
387            obj_ref.0,
388            EffectsObjectChange {
389                input_state: ObjectIn::Exist((
390                    (obj_ref.1, obj_ref.2),
391                    Owner::AddressOwner(IotaAddress::default()),
392                )),
393                output_state: ObjectOut::ObjectWrite((
394                    obj_ref.2,
395                    Owner::AddressOwner(IotaAddress::default()),
396                )),
397                id_operation: IDOperation::None,
398            },
399        ))
400    }
401
402    fn unsafe_add_object_tombstone_for_testing(&mut self, obj_ref: ObjectRef) {
403        self.changed_objects.push((
404            obj_ref.0,
405            EffectsObjectChange {
406                input_state: ObjectIn::Exist((
407                    (obj_ref.1, obj_ref.2),
408                    Owner::AddressOwner(IotaAddress::default()),
409                )),
410                output_state: ObjectOut::NotExist,
411                id_operation: IDOperation::Deleted,
412            },
413        ))
414    }
415}
416
417impl TransactionEffectsV1 {
418    pub fn new(
419        status: ExecutionStatus,
420        executed_epoch: EpochId,
421        gas_used: GasCostSummary,
422        shared_objects: Vec<SharedInput>,
423        loaded_per_epoch_config_objects: BTreeSet<ObjectID>,
424        transaction_digest: TransactionDigest,
425        lamport_version: SequenceNumber,
426        changed_objects: BTreeMap<ObjectID, EffectsObjectChange>,
427        gas_object: Option<ObjectID>,
428        events_digest: Option<TransactionEventsDigest>,
429        dependencies: Vec<TransactionDigest>,
430    ) -> Self {
431        let unchanged_shared_objects = shared_objects
432            .into_iter()
433            .filter_map(|shared_input| match shared_input {
434                SharedInput::Existing((id, version, digest)) => {
435                    if changed_objects.contains_key(&id) {
436                        None
437                    } else {
438                        Some((id, UnchangedSharedKind::ReadOnlyRoot((version, digest))))
439                    }
440                }
441                SharedInput::Deleted((id, version, mutable, _)) => {
442                    debug_assert!(!changed_objects.contains_key(&id));
443                    if mutable {
444                        Some((id, UnchangedSharedKind::MutateDeleted(version)))
445                    } else {
446                        Some((id, UnchangedSharedKind::ReadDeleted(version)))
447                    }
448                }
449                SharedInput::Cancelled((id, version)) => {
450                    debug_assert!(!changed_objects.contains_key(&id));
451                    Some((id, UnchangedSharedKind::Cancelled(version)))
452                }
453            })
454            .chain(
455                loaded_per_epoch_config_objects
456                    .into_iter()
457                    .map(|id| (id, UnchangedSharedKind::PerEpochConfig)),
458            )
459            .collect();
460        let changed_objects: Vec<_> = changed_objects.into_iter().collect();
461
462        let gas_object_index = gas_object.map(|gas_id| {
463            changed_objects
464                .iter()
465                .position(|(id, _)| id == &gas_id)
466                .unwrap() as u32
467        });
468
469        let result = Self {
470            status,
471            executed_epoch,
472            gas_used,
473            transaction_digest,
474            lamport_version,
475            changed_objects,
476            unchanged_shared_objects,
477            gas_object_index,
478            events_digest,
479            dependencies,
480            aux_data_digest: None,
481        };
482        #[cfg(debug_assertions)]
483        result.check_invariant();
484
485        result
486    }
487
488    /// This function demonstrates what's the invariant of the effects.
489    /// It also documents the semantics of different combinations in object
490    /// changes.
491    #[cfg(debug_assertions)]
492    fn check_invariant(&self) {
493        let mut unique_ids = HashSet::new();
494        for (id, change) in &self.changed_objects {
495            assert!(unique_ids.insert(*id));
496            match (
497                &change.input_state,
498                &change.output_state,
499                &change.id_operation,
500            ) {
501                (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Created) => {
502                    // created and then wrapped Move object.
503                }
504                (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => {
505                    // unwrapped and then deleted Move object.
506                }
507                (ObjectIn::NotExist, ObjectOut::ObjectWrite((_, owner)), IDOperation::None) => {
508                    // unwrapped Move object.
509                    // It's not allowed to make an object shared after unwrapping.
510                    assert!(!owner.is_shared());
511                }
512                (ObjectIn::NotExist, ObjectOut::ObjectWrite(..), IDOperation::Created) => {
513                    // created Move object.
514                }
515                (ObjectIn::NotExist, ObjectOut::PackageWrite(_), IDOperation::Created) => {
516                    // created Move package or user Move package upgrade.
517                }
518                (
519                    ObjectIn::Exist(((old_version, _), old_owner)),
520                    ObjectOut::NotExist,
521                    IDOperation::None,
522                ) => {
523                    // wrapped.
524                    assert!(old_version.value() < self.lamport_version.value());
525                    assert!(
526                        !old_owner.is_shared() && !old_owner.is_immutable(),
527                        "Cannot wrap shared or immutable object"
528                    );
529                }
530                (
531                    ObjectIn::Exist(((old_version, _), old_owner)),
532                    ObjectOut::NotExist,
533                    IDOperation::Deleted,
534                ) => {
535                    // deleted.
536                    assert!(old_version.value() < self.lamport_version.value());
537                    assert!(!old_owner.is_immutable(), "Cannot delete immutable object");
538                }
539                (
540                    ObjectIn::Exist(((old_version, old_digest), old_owner)),
541                    ObjectOut::ObjectWrite((new_digest, new_owner)),
542                    IDOperation::None,
543                ) => {
544                    // mutated.
545                    assert!(old_version.value() < self.lamport_version.value());
546                    assert_ne!(old_digest, new_digest);
547                    assert!(!old_owner.is_immutable(), "Cannot mutate immutable object");
548                    if old_owner.is_shared() {
549                        assert!(new_owner.is_shared(), "Cannot un-share an object");
550                    } else {
551                        assert!(!new_owner.is_shared(), "Cannot share an existing object");
552                    }
553                }
554                (
555                    ObjectIn::Exist(((old_version, old_digest), old_owner)),
556                    ObjectOut::PackageWrite((new_version, new_digest)),
557                    IDOperation::None,
558                ) => {
559                    // system package upgrade.
560                    assert!(
561                        old_owner.is_immutable() && is_system_package(*id),
562                        "Must be a system package"
563                    );
564                    assert_eq!(old_version.value() + 1, new_version.value());
565                    assert_ne!(old_digest, new_digest);
566                }
567                _ => {
568                    panic!("Impossible object change: {:?}, {:?}", id, change);
569                }
570            }
571        }
572        // Make sure that gas object exists in changed_objects.
573        let (_, owner) = self.gas_object();
574        assert!(matches!(owner, Owner::AddressOwner(_)));
575
576        for (id, _) in &self.unchanged_shared_objects {
577            assert!(
578                unique_ids.insert(*id),
579                "Duplicate object id: {:?}\n{:#?}",
580                id,
581                self
582            );
583        }
584    }
585
586    pub fn changed_objects(&self) -> &[(ObjectID, EffectsObjectChange)] {
587        &self.changed_objects
588    }
589
590    pub fn aux_data_digest(&self) -> Option<EffectsAuxDataDigest> {
591        self.aux_data_digest
592    }
593}
594
595impl Default for TransactionEffectsV1 {
596    fn default() -> Self {
597        Self {
598            status: ExecutionStatus::Success,
599            executed_epoch: 0,
600            gas_used: GasCostSummary::default(),
601            transaction_digest: TransactionDigest::default(),
602            lamport_version: SequenceNumber::default(),
603            changed_objects: vec![],
604            unchanged_shared_objects: vec![],
605            gas_object_index: None,
606            events_digest: None,
607            dependencies: vec![],
608            aux_data_digest: None,
609        }
610    }
611}
612
613#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
614pub enum UnchangedSharedKind {
615    /// Read-only shared objects from the input. We don't really need
616    /// ObjectDigest for protocol correctness, but it will make it easier to
617    /// verify untrusted read.
618    ReadOnlyRoot(VersionDigest),
619    /// Deleted shared objects that appear mutably/owned in the input.
620    MutateDeleted(SequenceNumber),
621    /// Deleted shared objects that appear as read-only in the input.
622    ReadDeleted(SequenceNumber),
623    /// Shared objects in cancelled transaction. The sequence number embed
624    /// cancellation reason.
625    Cancelled(SequenceNumber),
626    /// Read of a per-epoch config object that should remain the same during an
627    /// epoch.
628    PerEpochConfig,
629}