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    UnchangedSharedKind, UnchangedSharedObject, Version, crypto::Intent,
10};
11pub use iota_sdk_types::{
12    effects::{
13        ChangedObject as EffectsObjectChange, IdOperation as IDOperation, ObjectIn, ObjectOut,
14        TransactionEffects, TransactionEffectsV1,
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 for [`TransactionEffects`] that bypass the normal
242/// 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.
246
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    /// Returns the `(transaction_digest, effects_digest)` pair identifying
298    /// this execution.
299    fn execution_digests(&self) -> ExecutionDigests;
300
301    /// Return an iterator that iterates through all changed objects, including
302    /// mutated, created and unwrapped objects. In other words, all objects
303    /// that still exist in the object state after this transaction.
304    /// It doesn't include deleted/wrapped objects.
305    fn all_changed_objects(&self) -> Vec<(ObjectRef, Owner, WriteKind)>;
306
307    /// Return all objects that existed in the state prior to the transaction
308    /// but no longer exist in the state after the transaction.
309    /// It includes deleted and wrapped objects, but does not include
310    /// unwrapped_then_deleted objects.
311    fn all_removed_objects(&self) -> Vec<(ObjectRef, ObjectRemoveKind)>;
312
313    /// Returns all objects that will become a tombstone after this transaction.
314    /// This includes deleted, unwrapped_then_deleted and wrapped objects.
315    fn all_tombstones(&self) -> Vec<(ObjectId, SequenceNumber)>;
316
317    /// Returns all objects that were created + wrapped in the same transaction.
318    fn created_then_wrapped_objects(&self) -> Vec<(ObjectId, SequenceNumber)>;
319
320    /// Return an iterator of mutated objects, but excluding the gas object.
321    fn mutated_excluding_gas(&self) -> Vec<(ObjectRef, Owner)>;
322
323    /// Returns all affected objects in this transaction effects.
324    /// Affected objects include created, mutated, unwrapped, deleted,
325    /// unwrapped_then_deleted, wrapped and input shared objects.
326    fn all_affected_objects(&self) -> Vec<ObjectRef>;
327
328    /// Returns a condensed [`TransactionEffectsDebugSummary`] suitable for
329    /// logging and inspection.
330    fn summary_for_debug(&self) -> TransactionEffectsDebugSummary;
331
332    /// Upper-bound estimate of the serialized size in bytes of effects with
333    /// the given number of writes, modifies, and dependencies under the V1
334    /// protocol shape.
335    fn estimate_size_upperbound_v1(
336        num_writes: usize,
337        num_modifies: usize,
338        num_deps: usize,
339    ) -> usize {
340        let fixed_sizes = APPROX_SIZE_OF_EXECUTION_STATUS
341            + APPROX_SIZE_OF_EPOCH_ID
342            + APPROX_SIZE_OF_GAS_COST_SUMMARY
343            + APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST;
344
345        // We store object ref and owner for both old objects and new objects.
346        let approx_change_entry_size = 1_000
347            + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_writes
348            + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_modifies;
349
350        let deps_size = 1_000 + APPROX_SIZE_OF_TX_DIGEST * num_deps;
351
352        fixed_sizes + approx_change_entry_size + deps_size
353    }
354}
355
356/// Test-only counterpart to [`TransactionEffectsExt`]. Not for production use.
357pub trait TransactionEffectsExtForTesting: transaction_effects_ext::Sealed {
358    // All of these should be #[cfg(test)], but they are used by tests in other
359    // crates.
360
361    /// Build empty V1 effects for `transaction_digest`: success status, no
362    /// object changes, and no gas object. For tests that need a placeholder
363    /// whose effects content is irrelevant, e.g. system transactions.
364    fn new_empty_v1_for_testing(transaction_digest: TransactionDigest) -> Self;
365}
366
367// Helper macro to reduce boilerplate code
368macro_rules! delegate_effects_api {
369    ($self:ident, $method:ident $(, $arg:expr)*) => {
370        match $self {
371            TransactionEffects::V1(v1) => v1.$method($($arg),*),
372            _ => unimplemented!(
373                "a new TransactionEffects enum variant was added and needs to be handled"
374            ),
375        }
376    };
377}
378
379impl TransactionEffectsAPI for TransactionEffects {
380    fn status(&self) -> &ExecutionStatus {
381        delegate_effects_api!(self, status)
382    }
383
384    fn into_status(self) -> ExecutionStatus {
385        delegate_effects_api!(self, into_status)
386    }
387
388    fn epoch(&self) -> EpochId {
389        delegate_effects_api!(self, epoch)
390    }
391
392    fn modified_at_versions(&self) -> Vec<(ObjectId, Version)> {
393        delegate_effects_api!(self, modified_at_versions)
394    }
395
396    fn lamport_version(&self) -> Version {
397        delegate_effects_api!(self, lamport_version)
398    }
399
400    fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)> {
401        delegate_effects_api!(self, old_object_metadata)
402    }
403
404    fn input_shared_objects(&self) -> Vec<InputSharedObject> {
405        delegate_effects_api!(self, input_shared_objects)
406    }
407
408    fn created(&self) -> Vec<(ObjectRef, Owner)> {
409        delegate_effects_api!(self, created)
410    }
411
412    fn mutated(&self) -> Vec<(ObjectRef, Owner)> {
413        delegate_effects_api!(self, mutated)
414    }
415
416    fn unwrapped(&self) -> Vec<(ObjectRef, Owner)> {
417        delegate_effects_api!(self, unwrapped)
418    }
419
420    fn deleted(&self) -> Vec<ObjectRef> {
421        delegate_effects_api!(self, deleted)
422    }
423
424    fn unwrapped_then_deleted(&self) -> Vec<ObjectRef> {
425        delegate_effects_api!(self, unwrapped_then_deleted)
426    }
427
428    fn wrapped(&self) -> Vec<ObjectRef> {
429        delegate_effects_api!(self, wrapped)
430    }
431
432    fn object_changes(&self) -> Vec<ObjectChange> {
433        delegate_effects_api!(self, object_changes)
434    }
435
436    fn gas_object(&self) -> (ObjectRef, Owner) {
437        delegate_effects_api!(self, gas_object)
438    }
439
440    fn events_digest(&self) -> Option<&TransactionEventsDigest> {
441        delegate_effects_api!(self, events_digest)
442    }
443
444    fn dependencies(&self) -> &[TransactionDigest] {
445        delegate_effects_api!(self, dependencies)
446    }
447
448    fn transaction_digest(&self) -> &TransactionDigest {
449        delegate_effects_api!(self, transaction_digest)
450    }
451
452    fn gas_cost_summary(&self) -> &GasCostSummary {
453        delegate_effects_api!(self, gas_cost_summary)
454    }
455
456    fn unchanged_shared_objects(&self) -> Vec<(ObjectId, UnchangedSharedKind)> {
457        delegate_effects_api!(self, unchanged_shared_objects)
458    }
459}
460
461impl TransactionEffectsAPIForTesting for TransactionEffects {
462    fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus {
463        delegate_effects_api!(self, status_mut_for_testing)
464    }
465
466    fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary {
467        delegate_effects_api!(self, gas_cost_summary_mut_for_testing)
468    }
469
470    fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest {
471        delegate_effects_api!(self, transaction_digest_mut_for_testing)
472    }
473
474    fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest> {
475        delegate_effects_api!(self, dependencies_mut_for_testing)
476    }
477
478    fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject) {
479        delegate_effects_api!(self, unsafe_add_input_shared_object_for_testing, kind)
480    }
481
482    fn unsafe_add_deleted_live_object_for_testing(&mut self, object_ref: ObjectRef) {
483        delegate_effects_api!(self, unsafe_add_deleted_live_object_for_testing, object_ref)
484    }
485
486    fn unsafe_add_object_tombstone_for_testing(&mut self, object_ref: ObjectRef) {
487        delegate_effects_api!(self, unsafe_add_object_tombstone_for_testing, object_ref)
488    }
489}
490
491impl TransactionEffectsExt for TransactionEffects {
492    fn new_from_execution_v1(
493        status: ExecutionStatus,
494        epoch: EpochId,
495        gas_cost_summary: GasCostSummary,
496        shared_objects: Vec<SharedInput>,
497        loaded_per_epoch_config_objects: BTreeSet<ObjectId>,
498        transaction_digest: TransactionDigest,
499        lamport_version: SequenceNumber,
500        changed_objects: BTreeMap<ObjectId, EffectsObjectChange>,
501        gas_object: Option<ObjectId>,
502        events_digest: Option<TransactionEventsDigest>,
503        dependencies: Vec<TransactionDigest>,
504    ) -> Self {
505        TransactionEffects::V1(Box::new(v1::new_from_execution(
506            status,
507            epoch,
508            gas_cost_summary,
509            shared_objects,
510            loaded_per_epoch_config_objects,
511            transaction_digest,
512            lamport_version,
513            changed_objects,
514            gas_object,
515            events_digest,
516            dependencies,
517        )))
518    }
519
520    fn execution_digests(&self) -> ExecutionDigests {
521        ExecutionDigests {
522            transaction: *self.transaction_digest(),
523            effects: self.digest(),
524        }
525    }
526
527    fn all_changed_objects(&self) -> Vec<(ObjectRef, Owner, WriteKind)> {
528        self.mutated()
529            .into_iter()
530            .map(|(r, o)| (r, o, WriteKind::Mutate))
531            .chain(
532                self.created()
533                    .into_iter()
534                    .map(|(r, o)| (r, o, WriteKind::Create)),
535            )
536            .chain(
537                self.unwrapped()
538                    .into_iter()
539                    .map(|(r, o)| (r, o, WriteKind::Unwrap)),
540            )
541            .collect()
542    }
543
544    fn all_removed_objects(&self) -> Vec<(ObjectRef, ObjectRemoveKind)> {
545        self.deleted()
546            .iter()
547            .map(|obj_ref| (*obj_ref, ObjectRemoveKind::Delete))
548            .chain(
549                self.wrapped()
550                    .iter()
551                    .map(|obj_ref| (*obj_ref, ObjectRemoveKind::Wrap)),
552            )
553            .collect()
554    }
555
556    fn all_tombstones(&self) -> Vec<(ObjectId, SequenceNumber)> {
557        self.deleted()
558            .into_iter()
559            .chain(self.unwrapped_then_deleted())
560            .chain(self.wrapped())
561            .map(|obj_ref| (obj_ref.object_id, obj_ref.version))
562            .collect()
563    }
564
565    fn created_then_wrapped_objects(&self) -> Vec<(ObjectId, SequenceNumber)> {
566        // Filter `ObjectChange` where:
567        // - `input_digest` and `output_digest` are `None`, and
568        // - `id_operation` is `Created`.
569        self.object_changes()
570            .into_iter()
571            .filter_map(|change| {
572                if change.input_digest.is_none()
573                    && change.output_digest.is_none()
574                    && change.id_operation == IDOperation::Created
575                {
576                    Some((change.id, change.output_version.unwrap_or_default()))
577                } else {
578                    None
579                }
580            })
581            .collect::<Vec<_>>()
582    }
583
584    fn mutated_excluding_gas(&self) -> Vec<(ObjectRef, Owner)> {
585        self.mutated()
586            .into_iter()
587            .filter(|o| o != &self.gas_object())
588            .collect()
589    }
590
591    fn all_affected_objects(&self) -> Vec<ObjectRef> {
592        self.created()
593            .into_iter()
594            .map(|(r, _)| r)
595            .chain(self.mutated().into_iter().map(|(r, _)| r))
596            .chain(self.unwrapped().into_iter().map(|(r, _)| r))
597            .chain(
598                self.input_shared_objects()
599                    .into_iter()
600                    .map(|r| r.object_ref()),
601            )
602            .chain(self.deleted())
603            .chain(self.unwrapped_then_deleted())
604            .chain(self.wrapped())
605            .collect()
606    }
607
608    fn summary_for_debug(&self) -> TransactionEffectsDebugSummary {
609        TransactionEffectsDebugSummary {
610            bcs_size: bcs::serialized_size(self).unwrap(),
611            status: self.status().clone(),
612            gas_cost_summary: self.gas_cost_summary().clone(),
613            transaction_digest: *self.transaction_digest(),
614            created_object_count: self.created().len(),
615            mutated_object_count: self.mutated().len(),
616            unwrapped_object_count: self.unwrapped().len(),
617            deleted_object_count: self.deleted().len(),
618            wrapped_object_count: self.wrapped().len(),
619            dependency_count: self.dependencies().len(),
620        }
621    }
622}
623
624impl TransactionEffectsExtForTesting for TransactionEffects {
625    fn new_empty_v1_for_testing(transaction_digest: TransactionDigest) -> Self {
626        Self::new_from_execution_v1(
627            ExecutionStatus::Success,
628            0,
629            GasCostSummary::default(),
630            vec![],
631            BTreeSet::new(),
632            transaction_digest,
633            SequenceNumber::default(),
634            BTreeMap::new(),
635            None,
636            None,
637            vec![],
638        )
639    }
640}
641
642#[derive(Debug)]
643pub struct TransactionEffectsDebugSummary {
644    /// Size of bcs serialized bytes of the effects.
645    pub bcs_size: usize,
646    pub status: ExecutionStatus,
647    pub gas_cost_summary: GasCostSummary,
648    pub transaction_digest: TransactionDigest,
649    pub created_object_count: usize,
650    pub mutated_object_count: usize,
651    pub unwrapped_object_count: usize,
652    pub deleted_object_count: usize,
653    pub wrapped_object_count: usize,
654    pub dependency_count: usize,
655    // TODO: Add deleted_and_unwrapped_object_count and event digest.
656}
657
658pub type TransactionEffectsEnvelope<S> = Envelope<TransactionEffects, S>;
659pub type UnsignedTransactionEffects = TransactionEffectsEnvelope<EmptySignInfo>;
660pub type SignedTransactionEffects = TransactionEffectsEnvelope<AuthoritySignInfo>;
661pub type CertifiedTransactionEffects = TransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
662
663pub type TrustedSignedTransactionEffects = TrustedEnvelope<TransactionEffects, AuthoritySignInfo>;
664pub type VerifiedTransactionEffectsEnvelope<S> = VerifiedEnvelope<TransactionEffects, S>;
665pub type VerifiedSignedTransactionEffects = VerifiedTransactionEffectsEnvelope<AuthoritySignInfo>;
666pub type VerifiedCertifiedTransactionEffects =
667    VerifiedTransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
668
669impl CertifiedTransactionEffects {
670    #[instrument(level = "trace", skip_all)]
671    pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
672        self.auth_sig().verify_secure(
673            self.data(),
674            Intent::iota_app(IntentScope::TransactionEffects),
675            committee,
676        )
677    }
678
679    #[instrument(level = "trace", skip_all)]
680    pub fn verify(self, committee: &Committee) -> IotaResult<VerifiedCertifiedTransactionEffects> {
681        self.verify_authority_signatures(committee)?;
682        Ok(VerifiedCertifiedTransactionEffects::new_from_verified(self))
683    }
684}
685#[cfg(test)]
686mod tests {
687    use super::*;
688
689    /// `<TransactionEffects as Message>::digest` and the SDK's inherent
690    /// `TransactionEffects::digest` are defined independently in two crates.
691    /// They must agree: `Envelope<TransactionEffects, _>` resolves digests via
692    /// the trait, while direct call sites resolve to the inherent. Silent
693    /// divergence would split-brain storage and consensus digests.
694    #[test]
695    fn message_trait_and_effects_digest_match() {
696        let effects = TransactionEffects::new_empty_v1_for_testing(TransactionDigest::default());
697        let message_digest = <TransactionEffects as Message>::digest(&effects);
698        let effects_digest = effects.digest();
699        assert_eq!(message_digest, effects_digest);
700    }
701}