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;
10pub use object_change::{EffectsObjectChange, ObjectIn, ObjectOut};
11use serde::{Deserialize, Serialize};
12use shared_crypto::intent::{Intent, IntentScope};
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    /// Return an iterator of mutated objects, but excluding the gas object.
189    pub fn mutated_excluding_gas(&self) -> Vec<(ObjectRef, Owner)> {
190        self.mutated()
191            .into_iter()
192            .filter(|o| o != &self.gas_object())
193            .collect()
194    }
195
196    pub fn summary_for_debug(&self) -> TransactionEffectsDebugSummary {
197        TransactionEffectsDebugSummary {
198            bcs_size: bcs::serialized_size(self).unwrap(),
199            status: self.status().clone(),
200            gas_used: self.gas_cost_summary().clone(),
201            transaction_digest: *self.transaction_digest(),
202            created_object_count: self.created().len(),
203            mutated_object_count: self.mutated().len(),
204            unwrapped_object_count: self.unwrapped().len(),
205            deleted_object_count: self.deleted().len(),
206            wrapped_object_count: self.wrapped().len(),
207            dependency_count: self.dependencies().len(),
208        }
209    }
210}
211
212#[derive(Eq, PartialEq, Clone, Debug)]
213pub enum InputSharedObject {
214    Mutate(ObjectRef),
215    ReadOnly(ObjectRef),
216    ReadDeleted(ObjectID, SequenceNumber),
217    MutateDeleted(ObjectID, SequenceNumber),
218    Cancelled(ObjectID, SequenceNumber),
219}
220
221impl InputSharedObject {
222    pub fn id_and_version(&self) -> (ObjectID, SequenceNumber) {
223        let oref = self.object_ref();
224        (oref.0, oref.1)
225    }
226
227    pub fn object_ref(&self) -> ObjectRef {
228        match self {
229            InputSharedObject::Mutate(oref) | InputSharedObject::ReadOnly(oref) => *oref,
230            InputSharedObject::ReadDeleted(id, version)
231            | InputSharedObject::MutateDeleted(id, version) => {
232                (*id, *version, ObjectDigest::OBJECT_DIGEST_DELETED)
233            }
234            InputSharedObject::Cancelled(id, version) => {
235                (*id, *version, ObjectDigest::OBJECT_DIGEST_CANCELLED)
236            }
237        }
238    }
239}
240
241#[enum_dispatch]
242pub trait TransactionEffectsAPI {
243    fn status(&self) -> &ExecutionStatus;
244    fn into_status(self) -> ExecutionStatus;
245    fn executed_epoch(&self) -> EpochId;
246    fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)>;
247
248    /// The version assigned to all output objects (apart from packages).
249    fn lamport_version(&self) -> SequenceNumber;
250
251    /// Metadata of objects prior to modification. This includes any object that
252    /// exists in the store prior to this transaction and is modified in
253    /// this transaction. It includes objects that are mutated, wrapped and
254    /// deleted.
255    fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)>;
256    /// Returns the list of sequenced shared objects used in the input.
257    /// This is needed in effects because in transaction we only have object ID
258    /// for shared objects. Their version and digest can only be figured out
259    /// after sequencing. Also provides the use kind to indicate whether the
260    /// object was mutated or read-only. It does not include per epoch
261    /// config objects since they do not require sequencing. TODO: Rename
262    /// this function to indicate sequencing requirement.
263    fn input_shared_objects(&self) -> Vec<InputSharedObject>;
264    fn created(&self) -> Vec<(ObjectRef, Owner)>;
265    fn mutated(&self) -> Vec<(ObjectRef, Owner)>;
266    fn unwrapped(&self) -> Vec<(ObjectRef, Owner)>;
267    fn deleted(&self) -> Vec<ObjectRef>;
268    fn unwrapped_then_deleted(&self) -> Vec<ObjectRef>;
269    fn wrapped(&self) -> Vec<ObjectRef>;
270
271    fn object_changes(&self) -> Vec<ObjectChange>;
272
273    // TODO: We should consider having this function to return Option.
274    // When the gas object is not available (i.e. system transaction), we currently
275    // return dummy object ref and owner. This is not ideal.
276    fn gas_object(&self) -> (ObjectRef, Owner);
277
278    fn events_digest(&self) -> Option<&TransactionEventsDigest>;
279    fn dependencies(&self) -> &[TransactionDigest];
280
281    fn transaction_digest(&self) -> &TransactionDigest;
282
283    fn gas_cost_summary(&self) -> &GasCostSummary;
284
285    fn deleted_mutably_accessed_shared_objects(&self) -> Vec<ObjectID> {
286        self.input_shared_objects()
287            .into_iter()
288            .filter_map(|kind| match kind {
289                InputSharedObject::MutateDeleted(id, _) => Some(id),
290                InputSharedObject::Mutate(..)
291                | InputSharedObject::ReadOnly(..)
292                | InputSharedObject::ReadDeleted(..)
293                | InputSharedObject::Cancelled(..) => None,
294            })
295            .collect()
296    }
297
298    /// Returns all root shared objects (i.e. not child object) that are
299    /// read-only in the transaction.
300    fn unchanged_shared_objects(&self) -> Vec<(ObjectID, UnchangedSharedKind)>;
301
302    // All of these should be #[cfg(test)], but they are used by tests in other
303    // crates, and dependencies don't get built with cfg(test) set as far as I
304    // can tell.
305    fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus;
306    fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary;
307    fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest;
308    fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest>;
309    fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject);
310
311    // Adding an old version of a live object.
312    fn unsafe_add_deleted_live_object_for_testing(&mut self, obj_ref: ObjectRef);
313
314    // Adding a tombstone for a deleted object.
315    fn unsafe_add_object_tombstone_for_testing(&mut self, obj_ref: ObjectRef);
316}
317
318#[derive(Clone)]
319pub struct ObjectChange {
320    pub id: ObjectID,
321    pub input_version: Option<SequenceNumber>,
322    pub input_digest: Option<ObjectDigest>,
323    pub output_version: Option<SequenceNumber>,
324    pub output_digest: Option<ObjectDigest>,
325    pub id_operation: IDOperation,
326}
327
328#[derive(Eq, PartialEq, Copy, Clone, Debug, Serialize, Deserialize)]
329pub enum IDOperation {
330    None,
331    Created,
332    Deleted,
333}
334
335#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)]
336pub struct TransactionEvents {
337    pub data: Vec<Event>,
338}
339
340impl TransactionEvents {
341    pub fn digest(&self) -> TransactionEventsDigest {
342        TransactionEventsDigest::new(default_hash(self))
343    }
344}
345
346#[derive(Debug)]
347pub struct TransactionEffectsDebugSummary {
348    /// Size of bcs serialized bytes of the effects.
349    pub bcs_size: usize,
350    pub status: ExecutionStatus,
351    pub gas_used: GasCostSummary,
352    pub transaction_digest: TransactionDigest,
353    pub created_object_count: usize,
354    pub mutated_object_count: usize,
355    pub unwrapped_object_count: usize,
356    pub deleted_object_count: usize,
357    pub wrapped_object_count: usize,
358    pub dependency_count: usize,
359    // TODO: Add deleted_and_unwrapped_object_count and event digest.
360}
361
362pub type TransactionEffectsEnvelope<S> = Envelope<TransactionEffects, S>;
363pub type UnsignedTransactionEffects = TransactionEffectsEnvelope<EmptySignInfo>;
364pub type SignedTransactionEffects = TransactionEffectsEnvelope<AuthoritySignInfo>;
365pub type CertifiedTransactionEffects = TransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
366
367pub type TrustedSignedTransactionEffects = TrustedEnvelope<TransactionEffects, AuthoritySignInfo>;
368pub type VerifiedTransactionEffectsEnvelope<S> = VerifiedEnvelope<TransactionEffects, S>;
369pub type VerifiedSignedTransactionEffects = VerifiedTransactionEffectsEnvelope<AuthoritySignInfo>;
370pub type VerifiedCertifiedTransactionEffects =
371    VerifiedTransactionEffectsEnvelope<AuthorityStrongQuorumSignInfo>;
372
373impl CertifiedTransactionEffects {
374    pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
375        self.auth_sig().verify_secure(
376            self.data(),
377            Intent::iota_app(IntentScope::TransactionEffects),
378            committee,
379        )
380    }
381
382    pub fn verify(self, committee: &Committee) -> IotaResult<VerifiedCertifiedTransactionEffects> {
383        self.verify_authority_signatures(committee)?;
384        Ok(VerifiedCertifiedTransactionEffects::new_from_verified(self))
385    }
386}