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