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