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