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