Skip to main content

sui_types/effects/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4pub use self::effects_v2::TransactionEffectsV2;
5use crate::TypeTag;
6use crate::accumulator_event::AccumulatorEvent;
7use crate::base_types::{ExecutionDigests, ObjectID, ObjectRef, SequenceNumber};
8use crate::committee::EpochId;
9use crate::crypto::{AuthoritySignInfo, EmptySignInfo, default_hash};
10use crate::digests::{
11    ObjectDigest, TransactionDigest, TransactionEffectsDigest, TransactionEventsDigest,
12};
13use crate::event::Event;
14use crate::execution_status::{ExecutionStatus, MoveLocation};
15use crate::gas::GasCostSummary;
16use crate::message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope};
17use crate::object::Owner;
18use crate::storage::WriteKind;
19pub use effects_v1::TransactionEffectsV1;
20pub use effects_v2::UnchangedConsensusKind;
21use enum_dispatch::enum_dispatch;
22pub use object_change::{
23    AccumulatorAddress, AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1,
24    EffectsObjectChange, ObjectIn, ObjectOut,
25};
26use serde::{Deserialize, Serialize};
27use shared_crypto::intent::IntentScope;
28use std::collections::{BTreeMap, BTreeSet};
29pub use test_effects_builder::TestEffectsBuilder;
30
31mod effects_v1;
32mod effects_v2;
33mod object_change;
34mod test_effects_builder;
35
36// Since `std::mem::size_of` may not be stable across platforms, we use rough constants
37// We need these for estimating effects sizes
38// Approximate size of `ObjectRef` 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 = 120;
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 = 40;
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// Approximate size of an accumulator write's `changed_objects` entry, excluding its type tag
53// and event digests.
54pub const APPROX_SIZE_OF_ACCUMULATOR_WRITE: usize = 96;
55// Approximate size of one `(u64, Digest)` entry in `AccumulatorValue::EventDigest`.
56pub const APPROX_SIZE_OF_ACCUMULATOR_EVENT_DIGEST: usize = 48;
57// A `changed_objects` entry for an ID that was created or deleted, with no object read or
58// written, takes about this many bytes.
59pub const APPROX_SIZE_OF_ID_ONLY_CHANGE: usize = 40;
60// Approximate size of an `unchanged_consensus_objects` entry.
61pub const APPROX_SIZE_OF_UNCHANGED_CONSENSUS_OBJECT: usize = 80;
62
63/// The response from processing a transaction or a certified transaction
64#[enum_dispatch(TransactionEffectsAPI)]
65#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
66#[allow(clippy::large_enum_variant)]
67pub enum TransactionEffects {
68    V1(TransactionEffectsV1),
69    V2(TransactionEffectsV2),
70}
71
72impl Message for TransactionEffects {
73    type DigestType = TransactionEffectsDigest;
74    const SCOPE: IntentScope = IntentScope::TransactionEffects;
75
76    fn digest(&self) -> Self::DigestType {
77        TransactionEffectsDigest::new(default_hash(self))
78    }
79}
80
81// TODO: Get rid of this and use TestEffectsBuilder instead.
82impl Default for TransactionEffects {
83    fn default() -> Self {
84        TransactionEffects::V2(Default::default())
85    }
86}
87
88pub enum ObjectRemoveKind {
89    Delete,
90    Wrap,
91}
92
93impl TransactionEffects {
94    /// Creates a TransactionEffects message from the results of execution, choosing the correct
95    /// format for the current protocol version.
96    pub fn new_from_execution_v1(
97        status: ExecutionStatus,
98        executed_epoch: EpochId,
99        gas_used: GasCostSummary,
100        modified_at_versions: Vec<(ObjectID, SequenceNumber)>,
101        shared_objects: Vec<ObjectRef>,
102        transaction_digest: TransactionDigest,
103        created: Vec<(ObjectRef, Owner)>,
104        mutated: Vec<(ObjectRef, Owner)>,
105        unwrapped: Vec<(ObjectRef, Owner)>,
106        deleted: Vec<ObjectRef>,
107        unwrapped_then_deleted: Vec<ObjectRef>,
108        wrapped: Vec<ObjectRef>,
109        gas_object: (ObjectRef, Owner),
110        events_digest: Option<TransactionEventsDigest>,
111        dependencies: Vec<TransactionDigest>,
112    ) -> Self {
113        Self::V1(TransactionEffectsV1::new(
114            status,
115            executed_epoch,
116            gas_used,
117            modified_at_versions,
118            shared_objects,
119            transaction_digest,
120            created,
121            mutated,
122            unwrapped,
123            deleted,
124            unwrapped_then_deleted,
125            wrapped,
126            gas_object,
127            events_digest,
128            dependencies,
129        ))
130    }
131
132    /// Creates a TransactionEffects message from the results of execution, choosing the correct
133    /// format for the current protocol version.
134    pub fn new_from_execution_v2(
135        status: ExecutionStatus,
136        executed_epoch: EpochId,
137        gas_used: GasCostSummary,
138        unchanged_consensus_objects: Vec<(ObjectID, UnchangedConsensusKind)>,
139        transaction_digest: TransactionDigest,
140        lamport_version: SequenceNumber,
141        changed_objects: BTreeMap<ObjectID, EffectsObjectChange>,
142        gas_object: Option<ObjectID>,
143        events_digest: Option<TransactionEventsDigest>,
144        dependencies: Vec<TransactionDigest>,
145    ) -> Self {
146        Self::V2(TransactionEffectsV2::new(
147            status,
148            executed_epoch,
149            gas_used,
150            unchanged_consensus_objects,
151            transaction_digest,
152            lamport_version,
153            changed_objects,
154            gas_object,
155            events_digest,
156            dependencies,
157        ))
158    }
159
160    pub fn execution_digests(&self) -> ExecutionDigests {
161        ExecutionDigests {
162            transaction: *self.transaction_digest(),
163            effects: self.digest(),
164        }
165    }
166
167    pub fn estimate_effects_size_upperbound_v1(
168        num_writes: usize,
169        num_mutables: usize,
170        num_deletes: usize,
171        num_deps: usize,
172    ) -> usize {
173        let fixed_sizes = APPROX_SIZE_OF_EXECUTION_STATUS
174            + APPROX_SIZE_OF_EPOCH_ID
175            + APPROX_SIZE_OF_GAS_COST_SUMMARY
176            + APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST;
177
178        // Each write or delete contributes at roughly this amount because:
179        // Each write can be a mutation which can show up in `mutated` and `modified_at_versions`
180        // `num_delete` is added for padding
181        let approx_change_entry_size = 1_000
182            + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_writes
183            + (APPROX_SIZE_OF_OBJECT_REF * num_mutables)
184            + (APPROX_SIZE_OF_OBJECT_REF * num_deletes);
185
186        let deps_size = 1_000 + APPROX_SIZE_OF_TX_DIGEST * num_deps;
187
188        fixed_sizes + approx_change_entry_size + deps_size
189    }
190
191    /// When `complete_effects_size_estimate` is false, the ID-only change, unchanged
192    /// consensus object and accumulator event arguments are ignored.
193    pub fn estimate_effects_size_upperbound_v2(
194        num_writes: usize,
195        num_modifies: usize,
196        num_deps: usize,
197        num_id_only_changes: usize,
198        num_unchanged_consensus_objects: usize,
199        accumulator_events: &[AccumulatorEvent],
200        complete_effects_size_estimate: bool,
201    ) -> usize {
202        let fixed_sizes = APPROX_SIZE_OF_EXECUTION_STATUS
203            + APPROX_SIZE_OF_EPOCH_ID
204            + APPROX_SIZE_OF_GAS_COST_SUMMARY
205            + APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST;
206
207        // We store object ref and owner for both old objects and new objects.
208        let approx_change_entry_size = 1_000
209            + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_writes
210            + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_modifies;
211
212        let deps_size = 1_000 + APPROX_SIZE_OF_TX_DIGEST * num_deps;
213
214        let mut size = fixed_sizes + approx_change_entry_size + deps_size;
215
216        if complete_effects_size_estimate {
217            let id_only_changes_size =
218                num_id_only_changes.saturating_mul(APPROX_SIZE_OF_ID_ONLY_CHANGE);
219            let unchanged_consensus_objects_size = num_unchanged_consensus_objects
220                .saturating_mul(APPROX_SIZE_OF_UNCHANGED_CONSENSUS_OBJECT);
221            let accumulator_writes_size =
222                Self::estimate_accumulator_effects_size_upperbound(accumulator_events);
223
224            size = size
225                .saturating_add(id_only_changes_size)
226                .saturating_add(unchanged_consensus_objects_size)
227                .saturating_add(accumulator_writes_size);
228        }
229
230        size
231    }
232
233    /// Upper bound on the effects size of the accumulator writes after they are merged into one
234    /// write per accumulator object.
235    fn estimate_accumulator_effects_size_upperbound(
236        accumulator_events: &[AccumulatorEvent],
237    ) -> usize {
238        let mut seen = BTreeSet::new();
239        let mut size: usize = 0;
240        for event in accumulator_events {
241            if seen.insert(event.accumulator_obj) {
242                size = size.saturating_add(Self::estimate_accumulator_write_size_upperbound(
243                    &event.write.address.ty,
244                ));
245            }
246            let digests_size = match &event.write.value {
247                AccumulatorValue::Integer(_) | AccumulatorValue::IntegerTuple(_, _) => 0,
248                AccumulatorValue::EventDigest(digests) => digests
249                    .len()
250                    .saturating_mul(APPROX_SIZE_OF_ACCUMULATOR_EVENT_DIGEST),
251            };
252            size = size.saturating_add(digests_size);
253        }
254        size
255    }
256
257    /// Upper bound on the effects size of one accumulator object's write, excluding event digests.
258    pub fn estimate_accumulator_write_size_upperbound(ty: &TypeTag) -> usize {
259        bcs::serialized_size(ty)
260            .unwrap_or(usize::MAX)
261            .saturating_add(APPROX_SIZE_OF_ACCUMULATOR_WRITE)
262    }
263
264    /// Return an iterator that iterates through all changed objects, including mutated,
265    /// created and unwrapped objects. In other words, all objects that still exist
266    /// in the object state after this transaction.
267    /// It doesn't include deleted/wrapped objects.
268    pub fn all_changed_objects(&self) -> Vec<(ObjectRef, Owner, WriteKind)> {
269        self.mutated()
270            .into_iter()
271            .map(|(r, o)| (r, o, WriteKind::Mutate))
272            .chain(
273                self.created()
274                    .into_iter()
275                    .map(|(r, o)| (r, o, WriteKind::Create)),
276            )
277            .chain(
278                self.unwrapped()
279                    .into_iter()
280                    .map(|(r, o)| (r, o, WriteKind::Unwrap)),
281            )
282            .collect()
283    }
284
285    /// Return all objects that existed in the state prior to the transaction
286    /// but no longer exist in the state after the transaction.
287    /// It includes deleted and wrapped objects, but does not include unwrapped_then_deleted objects.
288    pub fn all_removed_objects(&self) -> Vec<(ObjectRef, ObjectRemoveKind)> {
289        self.deleted()
290            .iter()
291            .map(|obj_ref| (*obj_ref, ObjectRemoveKind::Delete))
292            .chain(
293                self.wrapped()
294                    .iter()
295                    .map(|obj_ref| (*obj_ref, ObjectRemoveKind::Wrap)),
296            )
297            .collect()
298    }
299
300    /// Returns all objects that will become a tombstone after this transaction.
301    /// This includes deleted, unwrapped_then_deleted and wrapped objects.
302    pub fn all_tombstones(&self) -> Vec<(ObjectID, SequenceNumber)> {
303        self.deleted()
304            .into_iter()
305            .chain(self.unwrapped_then_deleted())
306            .chain(self.wrapped())
307            .map(|obj_ref| (obj_ref.0, obj_ref.1))
308            .collect()
309    }
310
311    /// Return an iterator of mutated objects, but excluding the gas object.
312    pub fn mutated_excluding_gas(&self) -> Vec<(ObjectRef, Owner)> {
313        let gas_id = self.gas_object().map(|(oref, _)| oref.0);
314        self.mutated()
315            .into_iter()
316            .filter(|o| Some(o.0.0) != gas_id)
317            .collect()
318    }
319
320    pub fn summary_for_debug(&self) -> TransactionEffectsDebugSummary {
321        TransactionEffectsDebugSummary {
322            bcs_size: bcs::serialized_size(self).unwrap(),
323            status: self.status().clone(),
324            gas_used: self.gas_cost_summary().clone(),
325            transaction_digest: *self.transaction_digest(),
326            created_object_count: self.created().len(),
327            mutated_object_count: self.mutated().len(),
328            unwrapped_object_count: self.unwrapped().len(),
329            deleted_object_count: self.deleted().len(),
330            wrapped_object_count: self.wrapped().len(),
331            dependency_count: self.dependencies().len(),
332        }
333    }
334}
335
336#[derive(Eq, PartialEq, Clone, Debug)]
337pub enum InputConsensusObject {
338    Mutate(ObjectRef),
339    ReadOnly(ObjectRef),
340    ReadConsensusStreamEnded(ObjectID, SequenceNumber),
341    MutateConsensusStreamEnded(ObjectID, SequenceNumber),
342    Cancelled(ObjectID, SequenceNumber),
343}
344
345impl InputConsensusObject {
346    pub fn id_and_version(&self) -> (ObjectID, SequenceNumber) {
347        match self {
348            InputConsensusObject::Mutate(oref) | InputConsensusObject::ReadOnly(oref) => {
349                (oref.0, oref.1)
350            }
351            InputConsensusObject::ReadConsensusStreamEnded(id, version)
352            | InputConsensusObject::MutateConsensusStreamEnded(id, version) => (*id, *version),
353            InputConsensusObject::Cancelled(id, version) => (*id, *version),
354        }
355    }
356
357    // NOTE: When `ObjectDigest::OBJECT_DIGEST_DELETED` is returned, the object's consensus stream
358    // has ended, but it may not be deleted.
359    #[deprecated]
360    pub fn object_ref(&self) -> ObjectRef {
361        match self {
362            InputConsensusObject::Mutate(oref) | InputConsensusObject::ReadOnly(oref) => *oref,
363            InputConsensusObject::ReadConsensusStreamEnded(id, version)
364            | InputConsensusObject::MutateConsensusStreamEnded(id, version) => {
365                (*id, *version, ObjectDigest::OBJECT_DIGEST_DELETED)
366            }
367            InputConsensusObject::Cancelled(id, version) => {
368                (*id, *version, ObjectDigest::OBJECT_DIGEST_CANCELLED)
369            }
370        }
371    }
372}
373
374#[enum_dispatch]
375pub trait TransactionEffectsAPI {
376    fn status(&self) -> &ExecutionStatus;
377    fn into_status(self) -> ExecutionStatus;
378    fn executed_epoch(&self) -> EpochId;
379    fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)>;
380    fn move_abort(&self) -> Option<(MoveLocation, u64)>;
381
382    /// The version assigned to all output objects (apart from packages).
383    fn lamport_version(&self) -> SequenceNumber;
384
385    /// Metadata of objects prior to modification. This includes any object that exists in the
386    /// store prior to this transaction and is modified in this transaction.
387    /// It includes objects that are mutated, wrapped and deleted.
388    /// This API is only available on effects v2 and above.
389    fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)>;
390    /// Returns the consensus objects the transaction read or wrote, with the version and digest
391    /// resolved during execution (the transaction itself only carries the object ID for consensus
392    /// objects; version and digest are known only after sequencing). Each entry's kind indicates
393    /// whether the object was mutated or read-only.
394    ///
395    /// This includes system objects (e.g. the accumulator root) read during execution but not
396    /// declared as sequenced inputs — they are recorded as read-only so nodes executing from
397    /// effects can reproduce the read, and are indistinguishable here from genuinely-sequenced
398    /// read-only inputs. Callers
399    /// that need only the transaction's declared inputs must filter using the transaction's own
400    /// input set. It does not include per epoch config objects, since they do not require sequencing.
401    fn accessed_consensus_objects(&self) -> Vec<InputConsensusObject>;
402    fn created(&self) -> Vec<(ObjectRef, Owner)>;
403    fn mutated(&self) -> Vec<(ObjectRef, Owner)>;
404    fn unwrapped(&self) -> Vec<(ObjectRef, Owner)>;
405    fn deleted(&self) -> Vec<ObjectRef>;
406    fn unwrapped_then_deleted(&self) -> Vec<ObjectRef>;
407    fn wrapped(&self) -> Vec<ObjectRef>;
408    fn transferred_from_consensus(&self) -> Vec<ObjectRef>;
409    fn transferred_to_consensus(&self) -> Vec<ObjectRef>;
410    fn consensus_owner_changed(&self) -> Vec<ObjectRef>;
411
412    fn object_changes(&self) -> Vec<ObjectChange>;
413    fn published_packages(&self) -> Vec<ObjectID>;
414
415    /// The set of object refs written by this transaction, including deleted and wrapped objects.
416    /// Unlike object_changes(), returns no information about the starting state of the object.
417    fn written(&self) -> Vec<ObjectRef>;
418
419    fn accumulator_events(&self) -> Vec<AccumulatorEvent>;
420
421    /// Returns the gas object ref and owner. When the gas object was deleted (e.g. send_funds
422    /// consuming the gas coin), the object ID is preserved, the digest is set to
423    /// `ObjectDigest::OBJECT_DIGEST_DELETED` and the owner is set to a dummy address.
424    fn gas_object(&self) -> Option<(ObjectRef, Owner)>;
425
426    fn events_digest(&self) -> Option<&TransactionEventsDigest>;
427    fn dependencies(&self) -> &[TransactionDigest];
428
429    fn transaction_digest(&self) -> &TransactionDigest;
430
431    fn gas_cost_summary(&self) -> &GasCostSummary;
432
433    fn stream_ended_mutably_accessed_consensus_objects(&self) -> Vec<ObjectID> {
434        self.accessed_consensus_objects()
435            .into_iter()
436            .filter_map(|kind| match kind {
437                InputConsensusObject::MutateConsensusStreamEnded(id, _) => Some(id),
438                InputConsensusObject::Mutate(..)
439                | InputConsensusObject::ReadOnly(..)
440                | InputConsensusObject::ReadConsensusStreamEnded(..)
441                | InputConsensusObject::Cancelled(..) => None,
442            })
443            .collect()
444    }
445
446    /// Returns all root consensus objects (i.e. not child object) that are read-only in the transaction.
447    fn unchanged_consensus_objects(&self) -> Vec<(ObjectID, UnchangedConsensusKind)>;
448
449    /// Returns all accumulator updates in the transaction.
450    fn accumulator_updates(&self) -> Vec<(ObjectID, AccumulatorWriteV1)>;
451
452    // All of these should be #[cfg(test)], but they are used by tests in other crates, and
453    // dependencies don't get built with cfg(test) set as far as I can tell.
454    fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus;
455    fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary;
456    fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest;
457    fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest>;
458    fn unsafe_add_input_consensus_object_for_testing(&mut self, kind: InputConsensusObject);
459
460    // Adding an old version of a live object.
461    fn unsafe_add_deleted_live_object_for_testing(&mut self, obj_ref: ObjectRef);
462
463    // Adding a tombstone for a deleted object.
464    fn unsafe_add_object_tombstone_for_testing(&mut self, obj_ref: ObjectRef);
465}
466
467#[derive(Clone, Debug)]
468pub struct ObjectChange {
469    pub id: ObjectID,
470    pub input_version: Option<SequenceNumber>,
471    pub input_digest: Option<ObjectDigest>,
472    pub output_version: Option<SequenceNumber>,
473    pub output_digest: Option<ObjectDigest>,
474    pub id_operation: IDOperation,
475}
476
477#[derive(Eq, PartialEq, Copy, Clone, Debug, Serialize, Deserialize)]
478pub enum IDOperation {
479    None,
480    Created,
481    Deleted,
482}
483
484#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)]
485pub struct TransactionEvents {
486    pub data: Vec<Event>,
487}
488
489impl TransactionEvents {
490    pub fn digest(&self) -> TransactionEventsDigest {
491        TransactionEventsDigest::new(default_hash(self))
492    }
493}
494
495#[derive(Debug)]
496pub struct TransactionEffectsDebugSummary {
497    /// Size of bcs serialized byets of the effects.
498    pub bcs_size: usize,
499    pub status: ExecutionStatus,
500    pub gas_used: GasCostSummary,
501    pub transaction_digest: TransactionDigest,
502    pub created_object_count: usize,
503    pub mutated_object_count: usize,
504    pub unwrapped_object_count: usize,
505    pub deleted_object_count: usize,
506    pub wrapped_object_count: usize,
507    pub dependency_count: usize,
508    // TODO: Add deleted_and_unwrapped_object_count and event digest.
509}
510
511pub type TransactionEffectsEnvelope<S> = Envelope<TransactionEffects, S>;
512pub type UnsignedTransactionEffects = TransactionEffectsEnvelope<EmptySignInfo>;
513pub type SignedTransactionEffects = TransactionEffectsEnvelope<AuthoritySignInfo>;
514
515pub type TrustedSignedTransactionEffects = TrustedEnvelope<TransactionEffects, AuthoritySignInfo>;
516pub type VerifiedTransactionEffectsEnvelope<S> = VerifiedEnvelope<TransactionEffects, S>;
517pub type VerifiedSignedTransactionEffects = VerifiedTransactionEffectsEnvelope<AuthoritySignInfo>;
518
519#[cfg(test)]
520#[path = "../unit_tests/effects_tests.rs"]
521mod effects_tests;