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 effects_v1::TransactionEffectsV1;
8pub use effects_v1::UnchangedSharedKind;
9use enum_dispatch::enum_dispatch;
10use iota_sdk_types::crypto::{Intent, IntentScope};
11pub use object_change::{EffectsObjectChange, ObjectIn, ObjectOut};
12use serde::{Deserialize, Serialize};
13pub use test_effects_builder::TestEffectsBuilder;
14use tracing::instrument;
15
16use crate::{
17    base_types::{ExecutionDigests, ObjectID, ObjectRef, SequenceNumber},
18    committee::{Committee, EpochId},
19    crypto::{
20        AuthoritySignInfo, AuthoritySignInfoTrait, AuthorityStrongQuorumSignInfo, EmptySignInfo,
21        default_hash,
22    },
23    digests::{ObjectDigest, TransactionDigest, TransactionEffectsDigest, TransactionEventsDigest},
24    error::IotaResult,
25    event::Event,
26    execution::SharedInput,
27    execution_status::ExecutionStatus,
28    gas::GasCostSummary,
29    message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope},
30    object::Owner,
31    storage::WriteKind,
32};
33
34pub(crate) mod effects_v1;
35mod object_change;
36mod test_effects_builder;
37
38// Since `std::mem::size_of` may not be stable across platforms, we use rough
39// constants We need these for estimating effects sizes
40// Approximate size of `ObjectRef` type in bytes
41pub const APPROX_SIZE_OF_OBJECT_REF: usize = 80;
42// Approximate size of `ExecutionStatus` type in bytes
43pub const APPROX_SIZE_OF_EXECUTION_STATUS: usize = 120;
44// Approximate size of `EpochId` type in bytes
45pub const APPROX_SIZE_OF_EPOCH_ID: usize = 10;
46// Approximate size of `GasCostSummary` type in bytes
47pub const APPROX_SIZE_OF_GAS_COST_SUMMARY: usize = 50;
48// Approximate size of `Option<TransactionEventsDigest>` type in bytes
49pub const APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST: usize = 40;
50// Approximate size of `TransactionDigest` type in bytes
51pub const APPROX_SIZE_OF_TX_DIGEST: usize = 40;
52// Approximate size of `Owner` type in bytes
53pub const APPROX_SIZE_OF_OWNER: usize = 48;
54
55/// The response from processing a transaction or a certified transaction
56#[enum_dispatch(TransactionEffectsAPI)]
57#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
58pub enum TransactionEffects {
59    V1(TransactionEffectsV1),
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::V1(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,
85    /// choosing the correct 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        shared_objects: Vec<SharedInput>,
91        loaded_per_epoch_config_objects: BTreeSet<ObjectID>,
92        transaction_digest: TransactionDigest,
93        lamport_version: SequenceNumber,
94        changed_objects: BTreeMap<ObjectID, EffectsObjectChange>,
95        gas_object: Option<ObjectID>,
96        events_digest: Option<TransactionEventsDigest>,
97        dependencies: Vec<TransactionDigest>,
98    ) -> Self {
99        Self::V1(TransactionEffectsV1::new(
100            status,
101            executed_epoch,
102            gas_used,
103            shared_objects,
104            loaded_per_epoch_config_objects,
105            transaction_digest,
106            lamport_version,
107            changed_objects,
108            gas_object,
109            events_digest,
110            dependencies,
111        ))
112    }
113
114    pub fn execution_digests(&self) -> ExecutionDigests {
115        ExecutionDigests {
116            transaction: *self.transaction_digest(),
117            effects: self.digest(),
118        }
119    }
120
121    pub fn estimate_effects_size_upperbound_v1(
122        num_writes: usize,
123        num_modifies: usize,
124        num_deps: usize,
125    ) -> usize {
126        let fixed_sizes = APPROX_SIZE_OF_EXECUTION_STATUS
127            + APPROX_SIZE_OF_EPOCH_ID
128            + APPROX_SIZE_OF_GAS_COST_SUMMARY
129            + APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST;
130
131        // We store object ref and owner for both old objects and new objects.
132        let approx_change_entry_size = 1_000
133            + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_writes
134            + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_modifies;
135
136        let deps_size = 1_000 + APPROX_SIZE_OF_TX_DIGEST * num_deps;
137
138        fixed_sizes + approx_change_entry_size + deps_size
139    }
140
141    /// Return an iterator that iterates through all changed objects, including
142    /// mutated, created and unwrapped objects. In other words, all objects
143    /// that still exist in the object state after this transaction.
144    /// It doesn't include deleted/wrapped objects.
145    pub fn all_changed_objects(&self) -> Vec<(ObjectRef, Owner, WriteKind)> {
146        self.mutated()
147            .into_iter()
148            .map(|(r, o)| (r, o, WriteKind::Mutate))
149            .chain(
150                self.created()
151                    .into_iter()
152                    .map(|(r, o)| (r, o, WriteKind::Create)),
153            )
154            .chain(
155                self.unwrapped()
156                    .into_iter()
157                    .map(|(r, o)| (r, o, WriteKind::Unwrap)),
158            )
159            .collect()
160    }
161
162    /// Return all objects that existed in the state prior to the transaction
163    /// but no longer exist in the state after the transaction.
164    /// It includes deleted and wrapped objects, but does not include
165    /// unwrapped_then_deleted objects.
166    pub fn all_removed_objects(&self) -> Vec<(ObjectRef, ObjectRemoveKind)> {
167        self.deleted()
168            .iter()
169            .map(|obj_ref| (*obj_ref, ObjectRemoveKind::Delete))
170            .chain(
171                self.wrapped()
172                    .iter()
173                    .map(|obj_ref| (*obj_ref, ObjectRemoveKind::Wrap)),
174            )
175            .collect()
176    }
177
178    /// Returns all objects that will become a tombstone after this transaction.
179    /// This includes deleted, unwrapped_then_deleted and wrapped objects.
180    pub fn all_tombstones(&self) -> Vec<(ObjectID, SequenceNumber)> {
181        self.deleted()
182            .into_iter()
183            .chain(self.unwrapped_then_deleted())
184            .chain(self.wrapped())
185            .map(|obj_ref| (obj_ref.0, obj_ref.1))
186            .collect()
187    }
188
189    /// Returns all objects that were created + wrapped in the same transaction.
190    pub fn created_then_wrapped_objects(&self) -> Vec<(ObjectID, SequenceNumber)> {
191        // Filter `ObjectChange` where:
192        // - `input_digest` and `output_digest` are `None`, and
193        // - `id_operation` is `Created`.
194        self.object_changes()
195            .into_iter()
196            .filter_map(|change| {
197                if change.input_digest.is_none()
198                    && change.output_digest.is_none()
199                    && change.id_operation == IDOperation::Created
200                {
201                    Some((change.id, change.output_version.unwrap_or_default()))
202                } else {
203                    None
204                }
205            })
206            .collect::<Vec<_>>()
207    }
208
209    /// Return an iterator of mutated objects, but excluding the gas object.
210    pub fn mutated_excluding_gas(&self) -> Vec<(ObjectRef, Owner)> {
211        self.mutated()
212            .into_iter()
213            .filter(|o| o != &self.gas_object())
214            .collect()
215    }
216
217    /// Returns all affected objects in this transaction effects.
218    /// Affected objects include created, mutated, unwrapped, deleted,
219    /// unwrapped_then_deleted, wrapped and input shared objects.
220    pub fn all_affected_objects(&self) -> Vec<ObjectRef> {
221        self.created()
222            .into_iter()
223            .map(|(r, _)| r)
224            .chain(self.mutated().into_iter().map(|(r, _)| r))
225            .chain(self.unwrapped().into_iter().map(|(r, _)| r))
226            .chain(
227                self.input_shared_objects()
228                    .into_iter()
229                    .map(|r| r.object_ref()),
230            )
231            .chain(self.deleted())
232            .chain(self.unwrapped_then_deleted())
233            .chain(self.wrapped())
234            .collect()
235    }
236
237    pub fn summary_for_debug(&self) -> TransactionEffectsDebugSummary {
238        TransactionEffectsDebugSummary {
239            bcs_size: bcs::serialized_size(self).unwrap(),
240            status: self.status().clone(),
241            gas_used: self.gas_cost_summary().clone(),
242            transaction_digest: *self.transaction_digest(),
243            created_object_count: self.created().len(),
244            mutated_object_count: self.mutated().len(),
245            unwrapped_object_count: self.unwrapped().len(),
246            deleted_object_count: self.deleted().len(),
247            wrapped_object_count: self.wrapped().len(),
248            dependency_count: self.dependencies().len(),
249        }
250    }
251}
252
253#[derive(Eq, PartialEq, Clone, Debug)]
254pub enum InputSharedObject {
255    Mutate(ObjectRef),
256    ReadOnly(ObjectRef),
257    ReadDeleted(ObjectID, SequenceNumber),
258    MutateDeleted(ObjectID, SequenceNumber),
259    Cancelled(ObjectID, SequenceNumber),
260}
261
262impl InputSharedObject {
263    pub fn id_and_version(&self) -> (ObjectID, SequenceNumber) {
264        let oref = self.object_ref();
265        (oref.0, oref.1)
266    }
267
268    pub fn object_ref(&self) -> ObjectRef {
269        match self {
270            InputSharedObject::Mutate(oref) | InputSharedObject::ReadOnly(oref) => *oref,
271            InputSharedObject::ReadDeleted(id, version)
272            | InputSharedObject::MutateDeleted(id, version) => {
273                (*id, *version, ObjectDigest::OBJECT_DIGEST_DELETED)
274            }
275            InputSharedObject::Cancelled(id, version) => {
276                (*id, *version, ObjectDigest::OBJECT_DIGEST_CANCELLED)
277            }
278        }
279    }
280}
281
282#[enum_dispatch]
283pub trait TransactionEffectsAPI {
284    fn status(&self) -> &ExecutionStatus;
285    fn into_status(self) -> ExecutionStatus;
286    fn executed_epoch(&self) -> EpochId;
287    fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)>;
288
289    /// The version assigned to all output objects (apart from packages).
290    fn lamport_version(&self) -> SequenceNumber;
291
292    /// Metadata of objects prior to modification. This includes any object that
293    /// exists in the store prior to this transaction and is modified in
294    /// this transaction. It includes objects that are mutated, wrapped and
295    /// deleted.
296    fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)>;
297    /// Returns the list of sequenced shared objects used in the input.
298    /// This is needed in effects because in transaction we only have object ID
299    /// for shared objects. Their version and digest can only be figured out
300    /// after sequencing. Also provides the use kind to indicate whether the
301    /// object was mutated or read-only. It does not include per epoch
302    /// config objects since they do not require sequencing. TODO: Rename
303    /// this function to indicate sequencing requirement.
304    fn input_shared_objects(&self) -> Vec<InputSharedObject>;
305    fn created(&self) -> Vec<(ObjectRef, Owner)>;
306    fn mutated(&self) -> Vec<(ObjectRef, Owner)>;
307    fn unwrapped(&self) -> Vec<(ObjectRef, Owner)>;
308    fn deleted(&self) -> Vec<ObjectRef>;
309    fn unwrapped_then_deleted(&self) -> Vec<ObjectRef>;
310    fn wrapped(&self) -> Vec<ObjectRef>;
311
312    fn object_changes(&self) -> Vec<ObjectChange>;
313
314    // TODO: We should consider having this function to return Option.
315    // When the gas object is not available (i.e. system transaction), we currently
316    // return dummy object ref and owner. This is not ideal.
317    fn gas_object(&self) -> (ObjectRef, Owner);
318
319    fn events_digest(&self) -> Option<&TransactionEventsDigest>;
320    fn dependencies(&self) -> &[TransactionDigest];
321
322    fn transaction_digest(&self) -> &TransactionDigest;
323
324    fn gas_cost_summary(&self) -> &GasCostSummary;
325
326    fn deleted_mutably_accessed_shared_objects(&self) -> Vec<ObjectID> {
327        self.input_shared_objects()
328            .into_iter()
329            .filter_map(|kind| match kind {
330                InputSharedObject::MutateDeleted(id, _) => Some(id),
331                InputSharedObject::Mutate(..)
332                | InputSharedObject::ReadOnly(..)
333                | InputSharedObject::ReadDeleted(..)
334                | InputSharedObject::Cancelled(..) => None,
335            })
336            .collect()
337    }
338
339    /// Returns all root shared objects (i.e. not child object) that are
340    /// read-only in the transaction.
341    fn unchanged_shared_objects(&self) -> Vec<(ObjectID, UnchangedSharedKind)>;
342
343    // All of these should be #[cfg(test)], but they are used by tests in other
344    // crates, and dependencies don't get built with cfg(test) set as far as I
345    // can tell.
346    fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus;
347    fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary;
348    fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest;
349    fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest>;
350    fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject);
351
352    // Adding an old version of a live object.
353    fn unsafe_add_deleted_live_object_for_testing(&mut self, obj_ref: ObjectRef);
354
355    // Adding a tombstone for a deleted object.
356    fn unsafe_add_object_tombstone_for_testing(&mut self, obj_ref: ObjectRef);
357}
358
359#[derive(Clone)]
360pub struct ObjectChange {
361    pub id: ObjectID,
362    pub input_version: Option<SequenceNumber>,
363    pub input_digest: Option<ObjectDigest>,
364    pub output_version: Option<SequenceNumber>,
365    pub output_digest: Option<ObjectDigest>,
366    pub id_operation: IDOperation,
367}
368
369#[derive(Eq, PartialEq, Copy, Clone, Debug, Serialize, Deserialize)]
370pub enum IDOperation {
371    None,
372    Created,
373    Deleted,
374}
375
376#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)]
377pub struct TransactionEvents {
378    pub data: Vec<Event>,
379}
380
381impl TransactionEvents {
382    pub fn digest(&self) -> TransactionEventsDigest {
383        TransactionEventsDigest::new(default_hash(self))
384    }
385}
386
387#[derive(Debug)]
388pub struct TransactionEffectsDebugSummary {
389    /// Size of bcs serialized bytes of the effects.
390    pub bcs_size: usize,
391    pub status: ExecutionStatus,
392    pub gas_used: GasCostSummary,
393    pub transaction_digest: TransactionDigest,
394    pub created_object_count: usize,
395    pub mutated_object_count: usize,
396    pub unwrapped_object_count: usize,
397    pub deleted_object_count: usize,
398    pub wrapped_object_count: usize,
399    pub dependency_count: usize,
400    // TODO: Add deleted_and_unwrapped_object_count and event digest.
401}
402
403pub type TransactionEffectsEnvelope<S> = Envelope<TransactionEffects, S>;
404pub type UnsignedTransactionEffects = TransactionEffectsEnvelope<EmptySignInfo>;
405pub type SignedTransactionEffects = TransactionEffectsEnvelope<AuthoritySignInfo>;
406pub type CertifiedTransactionEffects = TransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
407
408pub type TrustedSignedTransactionEffects = TrustedEnvelope<TransactionEffects, AuthoritySignInfo>;
409pub type VerifiedTransactionEffectsEnvelope<S> = VerifiedEnvelope<TransactionEffects, S>;
410pub type VerifiedSignedTransactionEffects = VerifiedTransactionEffectsEnvelope<AuthoritySignInfo>;
411pub type VerifiedCertifiedTransactionEffects =
412    VerifiedTransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
413
414impl CertifiedTransactionEffects {
415    #[instrument(level = "trace", skip_all)]
416    pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
417        self.auth_sig().verify_secure(
418            self.data(),
419            Intent::iota_app(IntentScope::TransactionEffects),
420            committee,
421        )
422    }
423
424    #[instrument(level = "trace", skip_all)]
425    pub fn verify(self, committee: &Committee) -> IotaResult<VerifiedCertifiedTransactionEffects> {
426        self.verify_authority_signatures(committee)?;
427        Ok(VerifiedCertifiedTransactionEffects::new_from_verified(self))
428    }
429}