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