Skip to main content

iota_types/effects/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::{BTreeMap, BTreeSet};
6
7use iota_sdk_types::{
8    Digest, EpochId, ExecutionStatus, GasCostSummary, IntentScope, ObjectId, Owner,
9    UnchangedSharedObject, Version, crypto::Intent,
10};
11pub use iota_sdk_types::{
12    effects::{
13        ChangedObject as EffectsObjectChange, IdOperation as IDOperation, ObjectIn, ObjectOut,
14        TransactionEffects, TransactionEffectsV1, UnchangedSharedKind,
15    },
16    events::TransactionEvents,
17};
18pub use test_effects_builder::TestEffectsBuilder;
19use tracing::instrument;
20
21use crate::{
22    base_types::{ExecutionDigests, ObjectRef, SequenceNumber},
23    committee::Committee,
24    crypto::{
25        AuthoritySignInfo, AuthoritySignInfoTrait, AuthorityStrongQuorumSignInfo, EmptySignInfo,
26        default_hash,
27    },
28    digests::{TransactionDigest, TransactionEffectsDigest, TransactionEventsDigest},
29    error::IotaResult,
30    execution::SharedInput,
31    message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope},
32    storage::WriteKind,
33};
34
35mod test_effects_builder;
36mod v1;
37
38// Since `std::mem::size_of` may not be stable across platforms, we use rough
39// constants We need these for estimating effects sizes
40// Approximate size of `ObjectRef` type in bytes
41pub const APPROX_SIZE_OF_OBJECT_REF: usize = 80;
42// Approximate size of `ExecutionStatus` type in bytes
43pub const APPROX_SIZE_OF_EXECUTION_STATUS: usize = 144;
44// Approximate size of `EpochId` type in bytes
45pub const APPROX_SIZE_OF_EPOCH_ID: usize = 10;
46// Approximate size of `GasCostSummary` type in bytes
47pub const APPROX_SIZE_OF_GAS_COST_SUMMARY: usize = 50;
48// Approximate size of `Option<TransactionEventsDigest>` type in bytes
49pub const APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST: usize = 40;
50// Approximate size of `TransactionDigest` type in bytes
51pub const APPROX_SIZE_OF_TX_DIGEST: usize = 40;
52// Approximate size of `Owner` type in bytes
53pub const APPROX_SIZE_OF_OWNER: usize = 48;
54
55impl Message for TransactionEffects {
56    type DigestType = TransactionEffectsDigest;
57    const SCOPE: IntentScope = IntentScope::TransactionEffects;
58
59    fn digest(&self) -> Self::DigestType {
60        TransactionEffectsDigest::new(default_hash(self))
61    }
62}
63
64pub enum ObjectRemoveKind {
65    Delete,
66    Wrap,
67}
68
69/// Description of a shared object that was used as input to a transaction.
70///
71/// Captures how each shared object was accessed during execution: whether it
72/// was mutated, read-only, deleted after mutable or read-only access, or
73/// cancelled.
74#[derive(Eq, PartialEq, Copy, Clone, Debug)]
75pub enum InputSharedObject {
76    Mutate(ObjectRef),
77    ReadOnly(ObjectRef),
78    ReadDeleted(ObjectId, Version),
79    MutateDeleted(ObjectId, Version),
80    Cancelled(ObjectId, Version),
81}
82
83impl InputSharedObject {
84    pub fn id_and_version(&self) -> (ObjectId, Version) {
85        let (object_id, version, ..) = self.object_ref().into_parts();
86        (object_id, version)
87    }
88
89    pub fn object_ref(&self) -> ObjectRef {
90        match self {
91            InputSharedObject::Mutate(oref) | InputSharedObject::ReadOnly(oref) => *oref,
92            InputSharedObject::ReadDeleted(id, version)
93            | InputSharedObject::MutateDeleted(id, version) => {
94                ObjectRef::new(*id, *version, Digest::OBJECT_DELETED)
95            }
96            InputSharedObject::Cancelled(id, version) => {
97                ObjectRef::new(*id, *version, Digest::OBJECT_CANCELLED)
98            }
99        }
100    }
101}
102
103/// Effect on an individual object, keyed by its [`ObjectId`].
104///
105/// Describes the input and output version/digest of a single object that was
106/// read or modified during transaction execution, along with the
107/// [`IDOperation`] that was applied to it. This is a flattened,
108/// version-agnostic view derived from the effects via
109/// [`TransactionEffectsAPI::object_changes`].
110#[derive(Eq, PartialEq, Copy, Clone, Debug)]
111pub struct ObjectChange {
112    pub id: ObjectId,
113    pub input_version: Option<Version>,
114    pub input_digest: Option<Digest>,
115    pub output_version: Option<Version>,
116    pub output_digest: Option<Digest>,
117    pub id_operation: IDOperation,
118}
119
120mod transaction_effects_api {
121    pub trait Sealed {}
122    impl Sealed for super::TransactionEffects {}
123    impl Sealed for super::TransactionEffectsV1 {}
124}
125
126/// Version-agnostic accessors for [`TransactionEffects`].
127///
128/// Sealed; implemented for the enum and each version struct. The enum impl
129/// dispatches to the active variant.
130pub trait TransactionEffectsAPI: transaction_effects_api::Sealed {
131    /// Return the status of the transaction.
132    fn status(&self) -> &ExecutionStatus;
133
134    /// Consume `self` and return the owned status of the transaction.
135    fn into_status(self) -> ExecutionStatus;
136
137    /// Return the epoch in which this transaction was executed.
138    fn epoch(&self) -> EpochId;
139
140    /// Return the `(ObjectId, Version)` pair, at their pre-execution version,
141    /// of every object that existed in the store before this transaction
142    /// and was modified by it (mutated, wrapped, or deleted).
143    fn modified_at_versions(&self) -> Vec<(ObjectId, Version)>;
144
145    /// The version assigned to all output objects (apart from packages).
146    fn lamport_version(&self) -> Version;
147
148    /// Metadata of objects prior to modification. This includes any object that
149    /// exists in the store prior to this transaction and is modified in
150    /// this transaction. It includes objects that are mutated, wrapped and
151    /// deleted.
152    fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)>;
153
154    /// Returns the list of sequenced shared objects used in the input.
155    /// This is needed in effects because in transaction we only have object ID
156    /// for shared objects. Their version and digest can only be figured out
157    /// after sequencing. Also provides the use kind to indicate whether the
158    /// object was mutated or read-only. It does not include per epoch
159    /// config objects since they do not require sequencing. TODO: Rename
160    /// this function to indicate sequencing requirement.
161    fn input_shared_objects(&self) -> Vec<InputSharedObject>;
162
163    /// Objects (Move objects and packages) newly created by this transaction,
164    /// paired with their owner. Excludes objects that were created and then
165    /// wrapped within the same transaction.
166    fn created(&self) -> Vec<(ObjectRef, Owner)>;
167
168    /// Objects that existed before this transaction and whose contents were
169    /// updated by it (in-place mutations and system package upgrades),
170    /// reported at their post-execution `(ObjectRef, Owner)`.
171    fn mutated(&self) -> Vec<(ObjectRef, Owner)>;
172
173    /// Objects that were wrapped inside another object before this transaction
174    /// and have been promoted back to top-level objects in the store by it.
175    fn unwrapped(&self) -> Vec<(ObjectRef, Owner)>;
176
177    /// Objects that existed before this transaction and were deleted by it.
178    /// References use the post-execution version and the
179    /// [`TransactionEffectsDigest::OBJECT_DELETED`] tombstone digest.
180    fn deleted(&self) -> Vec<ObjectRef>;
181
182    /// Objects that were unwrapped and then deleted within this same
183    /// transaction (i.e. did not exist as top-level objects either before
184    /// or after). References use the post-execution version and the
185    /// [`TransactionEffectsDigest::OBJECT_DELETED`] tombstone digest.
186    fn unwrapped_then_deleted(&self) -> Vec<ObjectRef>;
187
188    /// Objects that existed as top-level objects before this transaction and
189    /// have been wrapped inside another object by it (i.e. no longer visible
190    /// in the object store as top-level). References use the post-execution
191    /// version and the [`TransactionEffectsDigest::OBJECT_WRAPPED`] tombstone
192    /// digest.
193    fn wrapped(&self) -> Vec<ObjectRef>;
194
195    /// Returns a flattened view of every object change recorded in these
196    /// effects: for each touched object, the input and output version/digest
197    /// (when present) together with the [`IDOperation`] describing whether
198    /// the ID was created, deleted, or unchanged.
199    fn object_changes(&self) -> Vec<ObjectChange>;
200
201    /// Returns the post-execution reference and owner of the gas object.
202    // TODO: We should consider having this function to return Option.
203    // When the gas object is not available (i.e. system transaction), we currently
204    // return dummy object ref and owner. This is not ideal.
205    fn gas_object(&self) -> (ObjectRef, Owner);
206
207    /// Digest of the events emitted by this transaction, or `None` if it
208    /// emitted no events.
209    fn events_digest(&self) -> Option<&TransactionEventsDigest>;
210
211    /// Digests of the transactions this one depends on, i.e. transactions
212    /// that must be executed before this one for its inputs to be available.
213    fn dependencies(&self) -> &[TransactionDigest];
214
215    /// Digest of the transaction that produced these effects.
216    fn transaction_digest(&self) -> &TransactionDigest;
217
218    /// Return the gas cost summary of the transaction.
219    fn gas_cost_summary(&self) -> &GasCostSummary;
220
221    /// IDs of shared objects that were declared as mutable inputs by the
222    /// transaction but had already been deleted at the time of execution.
223    fn deleted_mutably_accessed_shared_objects(&self) -> Vec<ObjectId> {
224        self.input_shared_objects()
225            .into_iter()
226            .filter_map(|kind| match kind {
227                InputSharedObject::MutateDeleted(id, _) => Some(id),
228                InputSharedObject::Mutate(..)
229                | InputSharedObject::ReadOnly(..)
230                | InputSharedObject::ReadDeleted(..)
231                | InputSharedObject::Cancelled(..) => None,
232            })
233            .collect()
234    }
235
236    /// Returns all root shared objects (i.e. not child object) that are
237    /// read-only in the transaction.
238    fn unchanged_shared_objects(&self) -> Vec<(ObjectId, UnchangedSharedKind)>;
239}
240
241/// Test-only mutators and unchecked builders for [`TransactionEffects`] that
242/// bypass the normal invariants. Not for production use.
243pub trait TransactionEffectsAPIForTesting: TransactionEffectsAPI {
244    // All of these should be #[cfg(test)], but they are used by tests in other
245    // crates, and dependencies don't get built with cfg(test) set as far as I
246    // can tell.
247    /// Returns a mutable reference to the execution status, for tests.
248    fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus;
249
250    /// Returns a mutable reference to the gas cost summary, for tests.
251    fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary;
252
253    /// Returns a mutable reference to the transaction digest, for tests.
254    fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest;
255
256    /// Returns a mutable reference to the dependency list, for tests.
257    fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest>;
258
259    /// Records `kind` as an input shared object without validating that it is
260    /// consistent with the rest of the effects. For tests only.
261    fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject);
262
263    /// Records an entry that represents the pre-execution version of a still
264    /// live object, without validating consistency with the rest of the
265    /// effects. For tests only.
266    fn unsafe_add_deleted_live_object_for_testing(&mut self, object_ref: ObjectRef);
267
268    /// Records a tombstone entry for a deleted object, without validating
269    /// consistency with the rest of the effects. For tests only.
270    fn unsafe_add_object_tombstone_for_testing(&mut self, object_ref: ObjectRef);
271}
272
273mod transaction_effects_ext {
274    pub trait Sealed {}
275    impl Sealed for super::TransactionEffects {}
276}
277
278/// The version-selecting constructor and aggregating queries for the
279/// [`TransactionEffects`] enum. Sealed; implemented only for the enum.
280pub trait TransactionEffectsExt: transaction_effects_ext::Sealed {
281    /// Build effects from the results of executing a transaction under the
282    /// V1 protocol shape.
283    fn new_from_execution_v1(
284        status: ExecutionStatus,
285        epoch: EpochId,
286        gas_cost_summary: GasCostSummary,
287        shared_objects: Vec<SharedInput>,
288        loaded_per_epoch_config_objects: BTreeSet<ObjectId>,
289        transaction_digest: TransactionDigest,
290        lamport_version: SequenceNumber,
291        changed_objects: BTreeMap<ObjectId, EffectsObjectChange>,
292        gas_object: Option<ObjectId>,
293        events_digest: Option<TransactionEventsDigest>,
294        dependencies: Vec<TransactionDigest>,
295    ) -> Self;
296
297    /// Build empty V1 effects for `transaction_digest`: success status, no
298    /// object changes, and no gas object. For tests that need a placeholder
299    /// whose effects content is irrelevant, e.g. system transactions.
300    fn new_empty_v1(transaction_digest: TransactionDigest) -> Self;
301
302    /// Returns the `(transaction_digest, effects_digest)` pair identifying
303    /// this execution.
304    fn execution_digests(&self) -> ExecutionDigests;
305
306    /// Return an iterator that iterates through all changed objects, including
307    /// mutated, created and unwrapped objects. In other words, all objects
308    /// that still exist in the object state after this transaction.
309    /// It doesn't include deleted/wrapped objects.
310    fn all_changed_objects(&self) -> Vec<(ObjectRef, Owner, WriteKind)>;
311
312    /// Return all objects that existed in the state prior to the transaction
313    /// but no longer exist in the state after the transaction.
314    /// It includes deleted and wrapped objects, but does not include
315    /// unwrapped_then_deleted objects.
316    fn all_removed_objects(&self) -> Vec<(ObjectRef, ObjectRemoveKind)>;
317
318    /// Returns all objects that will become a tombstone after this transaction.
319    /// This includes deleted, unwrapped_then_deleted and wrapped objects.
320    fn all_tombstones(&self) -> Vec<(ObjectId, SequenceNumber)>;
321
322    /// Returns all objects that were created + wrapped in the same transaction.
323    fn created_then_wrapped_objects(&self) -> Vec<(ObjectId, SequenceNumber)>;
324
325    /// Return an iterator of mutated objects, but excluding the gas object.
326    fn mutated_excluding_gas(&self) -> Vec<(ObjectRef, Owner)>;
327
328    /// Returns all affected objects in this transaction effects.
329    /// Affected objects include created, mutated, unwrapped, deleted,
330    /// unwrapped_then_deleted, wrapped and input shared objects.
331    fn all_affected_objects(&self) -> Vec<ObjectRef>;
332
333    /// Returns a condensed [`TransactionEffectsDebugSummary`] suitable for
334    /// logging and inspection.
335    fn summary_for_debug(&self) -> TransactionEffectsDebugSummary;
336
337    /// Upper-bound estimate of the serialized size in bytes of effects with
338    /// the given number of writes, modifies, and dependencies under the V1
339    /// protocol shape.
340    fn estimate_size_upperbound_v1(
341        num_writes: usize,
342        num_modifies: usize,
343        num_deps: usize,
344    ) -> usize {
345        let fixed_sizes = APPROX_SIZE_OF_EXECUTION_STATUS
346            + APPROX_SIZE_OF_EPOCH_ID
347            + APPROX_SIZE_OF_GAS_COST_SUMMARY
348            + APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST;
349
350        // We store object ref and owner for both old objects and new objects.
351        let approx_change_entry_size = 1_000
352            + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_writes
353            + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_modifies;
354
355        let deps_size = 1_000 + APPROX_SIZE_OF_TX_DIGEST * num_deps;
356
357        fixed_sizes + approx_change_entry_size + deps_size
358    }
359}
360
361// Helper macro to reduce boilerplate code
362macro_rules! delegate_effects_api {
363    ($self:ident, $method:ident $(, $arg:expr)*) => {
364        match $self {
365            TransactionEffects::V1(v1) => v1.$method($($arg),*),
366            _ => unimplemented!(
367                "a new TransactionEffects enum variant was added and needs to be handled"
368            ),
369        }
370    };
371}
372
373impl TransactionEffectsAPI for TransactionEffects {
374    fn status(&self) -> &ExecutionStatus {
375        delegate_effects_api!(self, status)
376    }
377
378    fn into_status(self) -> ExecutionStatus {
379        delegate_effects_api!(self, into_status)
380    }
381
382    fn epoch(&self) -> EpochId {
383        delegate_effects_api!(self, epoch)
384    }
385
386    fn modified_at_versions(&self) -> Vec<(ObjectId, Version)> {
387        delegate_effects_api!(self, modified_at_versions)
388    }
389
390    fn lamport_version(&self) -> Version {
391        delegate_effects_api!(self, lamport_version)
392    }
393
394    fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)> {
395        delegate_effects_api!(self, old_object_metadata)
396    }
397
398    fn input_shared_objects(&self) -> Vec<InputSharedObject> {
399        delegate_effects_api!(self, input_shared_objects)
400    }
401
402    fn created(&self) -> Vec<(ObjectRef, Owner)> {
403        delegate_effects_api!(self, created)
404    }
405
406    fn mutated(&self) -> Vec<(ObjectRef, Owner)> {
407        delegate_effects_api!(self, mutated)
408    }
409
410    fn unwrapped(&self) -> Vec<(ObjectRef, Owner)> {
411        delegate_effects_api!(self, unwrapped)
412    }
413
414    fn deleted(&self) -> Vec<ObjectRef> {
415        delegate_effects_api!(self, deleted)
416    }
417
418    fn unwrapped_then_deleted(&self) -> Vec<ObjectRef> {
419        delegate_effects_api!(self, unwrapped_then_deleted)
420    }
421
422    fn wrapped(&self) -> Vec<ObjectRef> {
423        delegate_effects_api!(self, wrapped)
424    }
425
426    fn object_changes(&self) -> Vec<ObjectChange> {
427        delegate_effects_api!(self, object_changes)
428    }
429
430    fn gas_object(&self) -> (ObjectRef, Owner) {
431        delegate_effects_api!(self, gas_object)
432    }
433
434    fn events_digest(&self) -> Option<&TransactionEventsDigest> {
435        delegate_effects_api!(self, events_digest)
436    }
437
438    fn dependencies(&self) -> &[TransactionDigest] {
439        delegate_effects_api!(self, dependencies)
440    }
441
442    fn transaction_digest(&self) -> &TransactionDigest {
443        delegate_effects_api!(self, transaction_digest)
444    }
445
446    fn gas_cost_summary(&self) -> &GasCostSummary {
447        delegate_effects_api!(self, gas_cost_summary)
448    }
449
450    fn unchanged_shared_objects(&self) -> Vec<(ObjectId, UnchangedSharedKind)> {
451        delegate_effects_api!(self, unchanged_shared_objects)
452    }
453}
454
455impl TransactionEffectsAPIForTesting for TransactionEffects {
456    fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus {
457        delegate_effects_api!(self, status_mut_for_testing)
458    }
459
460    fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary {
461        delegate_effects_api!(self, gas_cost_summary_mut_for_testing)
462    }
463
464    fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest {
465        delegate_effects_api!(self, transaction_digest_mut_for_testing)
466    }
467
468    fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest> {
469        delegate_effects_api!(self, dependencies_mut_for_testing)
470    }
471
472    fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject) {
473        delegate_effects_api!(self, unsafe_add_input_shared_object_for_testing, kind)
474    }
475
476    fn unsafe_add_deleted_live_object_for_testing(&mut self, object_ref: ObjectRef) {
477        delegate_effects_api!(self, unsafe_add_deleted_live_object_for_testing, object_ref)
478    }
479
480    fn unsafe_add_object_tombstone_for_testing(&mut self, object_ref: ObjectRef) {
481        delegate_effects_api!(self, unsafe_add_object_tombstone_for_testing, object_ref)
482    }
483}
484
485impl TransactionEffectsExt for TransactionEffects {
486    fn new_from_execution_v1(
487        status: ExecutionStatus,
488        epoch: EpochId,
489        gas_cost_summary: GasCostSummary,
490        shared_objects: Vec<SharedInput>,
491        loaded_per_epoch_config_objects: BTreeSet<ObjectId>,
492        transaction_digest: TransactionDigest,
493        lamport_version: SequenceNumber,
494        changed_objects: BTreeMap<ObjectId, EffectsObjectChange>,
495        gas_object: Option<ObjectId>,
496        events_digest: Option<TransactionEventsDigest>,
497        dependencies: Vec<TransactionDigest>,
498    ) -> Self {
499        TransactionEffects::V1(Box::new(v1::new_from_execution(
500            status,
501            epoch,
502            gas_cost_summary,
503            shared_objects,
504            loaded_per_epoch_config_objects,
505            transaction_digest,
506            lamport_version,
507            changed_objects,
508            gas_object,
509            events_digest,
510            dependencies,
511        )))
512    }
513
514    fn new_empty_v1(transaction_digest: TransactionDigest) -> Self {
515        Self::new_from_execution_v1(
516            ExecutionStatus::Success,
517            0,
518            GasCostSummary::default(),
519            vec![],
520            BTreeSet::new(),
521            transaction_digest,
522            SequenceNumber::default(),
523            BTreeMap::new(),
524            None,
525            None,
526            vec![],
527        )
528    }
529
530    fn execution_digests(&self) -> ExecutionDigests {
531        ExecutionDigests {
532            transaction: *self.transaction_digest(),
533            effects: self.digest(),
534        }
535    }
536
537    fn all_changed_objects(&self) -> Vec<(ObjectRef, Owner, WriteKind)> {
538        self.mutated()
539            .into_iter()
540            .map(|(r, o)| (r, o, WriteKind::Mutate))
541            .chain(
542                self.created()
543                    .into_iter()
544                    .map(|(r, o)| (r, o, WriteKind::Create)),
545            )
546            .chain(
547                self.unwrapped()
548                    .into_iter()
549                    .map(|(r, o)| (r, o, WriteKind::Unwrap)),
550            )
551            .collect()
552    }
553
554    fn all_removed_objects(&self) -> Vec<(ObjectRef, ObjectRemoveKind)> {
555        self.deleted()
556            .iter()
557            .map(|obj_ref| (*obj_ref, ObjectRemoveKind::Delete))
558            .chain(
559                self.wrapped()
560                    .iter()
561                    .map(|obj_ref| (*obj_ref, ObjectRemoveKind::Wrap)),
562            )
563            .collect()
564    }
565
566    fn all_tombstones(&self) -> Vec<(ObjectId, SequenceNumber)> {
567        self.deleted()
568            .into_iter()
569            .chain(self.unwrapped_then_deleted())
570            .chain(self.wrapped())
571            .map(|obj_ref| (obj_ref.object_id, obj_ref.version))
572            .collect()
573    }
574
575    fn created_then_wrapped_objects(&self) -> Vec<(ObjectId, SequenceNumber)> {
576        // Filter `ObjectChange` where:
577        // - `input_digest` and `output_digest` are `None`, and
578        // - `id_operation` is `Created`.
579        self.object_changes()
580            .into_iter()
581            .filter_map(|change| {
582                if change.input_digest.is_none()
583                    && change.output_digest.is_none()
584                    && change.id_operation == IDOperation::Created
585                {
586                    Some((change.id, change.output_version.unwrap_or_default()))
587                } else {
588                    None
589                }
590            })
591            .collect::<Vec<_>>()
592    }
593
594    fn mutated_excluding_gas(&self) -> Vec<(ObjectRef, Owner)> {
595        self.mutated()
596            .into_iter()
597            .filter(|o| o != &self.gas_object())
598            .collect()
599    }
600
601    fn all_affected_objects(&self) -> Vec<ObjectRef> {
602        self.created()
603            .into_iter()
604            .map(|(r, _)| r)
605            .chain(self.mutated().into_iter().map(|(r, _)| r))
606            .chain(self.unwrapped().into_iter().map(|(r, _)| r))
607            .chain(
608                self.input_shared_objects()
609                    .into_iter()
610                    .map(|r| r.object_ref()),
611            )
612            .chain(self.deleted())
613            .chain(self.unwrapped_then_deleted())
614            .chain(self.wrapped())
615            .collect()
616    }
617
618    fn summary_for_debug(&self) -> TransactionEffectsDebugSummary {
619        TransactionEffectsDebugSummary {
620            bcs_size: bcs::serialized_size(self).unwrap(),
621            status: self.status().clone(),
622            gas_cost_summary: self.gas_cost_summary().clone(),
623            transaction_digest: *self.transaction_digest(),
624            created_object_count: self.created().len(),
625            mutated_object_count: self.mutated().len(),
626            unwrapped_object_count: self.unwrapped().len(),
627            deleted_object_count: self.deleted().len(),
628            wrapped_object_count: self.wrapped().len(),
629            dependency_count: self.dependencies().len(),
630        }
631    }
632}
633
634#[derive(Debug)]
635pub struct TransactionEffectsDebugSummary {
636    /// Size of bcs serialized bytes of the effects.
637    pub bcs_size: usize,
638    pub status: ExecutionStatus,
639    pub gas_cost_summary: GasCostSummary,
640    pub transaction_digest: TransactionDigest,
641    pub created_object_count: usize,
642    pub mutated_object_count: usize,
643    pub unwrapped_object_count: usize,
644    pub deleted_object_count: usize,
645    pub wrapped_object_count: usize,
646    pub dependency_count: usize,
647    // TODO: Add deleted_and_unwrapped_object_count and event digest.
648}
649
650pub type TransactionEffectsEnvelope<S> = Envelope<TransactionEffects, S>;
651pub type UnsignedTransactionEffects = TransactionEffectsEnvelope<EmptySignInfo>;
652pub type SignedTransactionEffects = TransactionEffectsEnvelope<AuthoritySignInfo>;
653pub type CertifiedTransactionEffects = TransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
654
655pub type TrustedSignedTransactionEffects = TrustedEnvelope<TransactionEffects, AuthoritySignInfo>;
656pub type VerifiedTransactionEffectsEnvelope<S> = VerifiedEnvelope<TransactionEffects, S>;
657pub type VerifiedSignedTransactionEffects = VerifiedTransactionEffectsEnvelope<AuthoritySignInfo>;
658pub type VerifiedCertifiedTransactionEffects =
659    VerifiedTransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
660
661impl CertifiedTransactionEffects {
662    #[instrument(level = "trace", skip_all)]
663    pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
664        self.auth_sig().verify_secure(
665            self.data(),
666            Intent::iota_app(IntentScope::TransactionEffects),
667            committee,
668        )
669    }
670
671    #[instrument(level = "trace", skip_all)]
672    pub fn verify(self, committee: &Committee) -> IotaResult<VerifiedCertifiedTransactionEffects> {
673        self.verify_authority_signatures(committee)?;
674        Ok(VerifiedCertifiedTransactionEffects::new_from_verified(self))
675    }
676}
677#[cfg(test)]
678mod tests {
679    use super::*;
680
681    /// `<TransactionEffects as Message>::digest` and the SDK's inherent
682    /// `TransactionEffects::digest` are defined independently in two crates.
683    /// They must agree: `Envelope<TransactionEffects, _>` resolves digests via
684    /// the trait, while direct call sites resolve to the inherent. Silent
685    /// divergence would split-brain storage and consensus digests.
686    #[test]
687    fn message_trait_and_effects_digest_match() {
688        let effects = TransactionEffects::new_empty_v1(TransactionDigest::default());
689        let message_digest = <TransactionEffects as Message>::digest(&effects);
690        let effects_digest = effects.digest();
691        assert_eq!(message_digest, effects_digest);
692    }
693}