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