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