diff --git a/crates/iota-core/tests/staged/iota.yaml b/crates/iota-core/tests/staged/iota.yaml index 97847f906cd..4aab914c386 100644 --- a/crates/iota-core/tests/staged/iota.yaml +++ b/crates/iota-core/tests/staged/iota.yaml @@ -868,89 +868,10 @@ TransactionEffects: V1: NEWTYPE: TYPENAME: TransactionEffectsV1 - 1: - V2: - NEWTYPE: - TYPENAME: TransactionEffectsV2 TransactionEffectsDigest: NEWTYPESTRUCT: TYPENAME: Digest TransactionEffectsV1: - STRUCT: - - status: - TYPENAME: ExecutionStatus - - executed_epoch: U64 - - gas_used: - TYPENAME: GasCostSummary - - modified_at_versions: - SEQ: - TUPLE: - - TYPENAME: ObjectID - - TYPENAME: SequenceNumber - - shared_objects: - SEQ: - TUPLE: - - TYPENAME: ObjectID - - TYPENAME: SequenceNumber - - TYPENAME: ObjectDigest - - transaction_digest: - TYPENAME: TransactionDigest - - created: - SEQ: - TUPLE: - - TUPLE: - - TYPENAME: ObjectID - - TYPENAME: SequenceNumber - - TYPENAME: ObjectDigest - - TYPENAME: Owner - - mutated: - SEQ: - TUPLE: - - TUPLE: - - TYPENAME: ObjectID - - TYPENAME: SequenceNumber - - TYPENAME: ObjectDigest - - TYPENAME: Owner - - unwrapped: - SEQ: - TUPLE: - - TUPLE: - - TYPENAME: ObjectID - - TYPENAME: SequenceNumber - - TYPENAME: ObjectDigest - - TYPENAME: Owner - - deleted: - SEQ: - TUPLE: - - TYPENAME: ObjectID - - TYPENAME: SequenceNumber - - TYPENAME: ObjectDigest - - unwrapped_then_deleted: - SEQ: - TUPLE: - - TYPENAME: ObjectID - - TYPENAME: SequenceNumber - - TYPENAME: ObjectDigest - - wrapped: - SEQ: - TUPLE: - - TYPENAME: ObjectID - - TYPENAME: SequenceNumber - - TYPENAME: ObjectDigest - - gas_object: - TUPLE: - - TUPLE: - - TYPENAME: ObjectID - - TYPENAME: SequenceNumber - - TYPENAME: ObjectDigest - - TYPENAME: Owner - - events_digest: - OPTION: - TYPENAME: TransactionEventsDigest - - dependencies: - SEQ: - TYPENAME: TransactionDigest -TransactionEffectsV2: STRUCT: - status: TYPENAME: ExecutionStatus diff --git a/crates/iota-open-rpc/spec/openrpc.json b/crates/iota-open-rpc/spec/openrpc.json index a62c56c7bca..9acb5367c22 100644 --- a/crates/iota-open-rpc/spec/openrpc.json +++ b/crates/iota-open-rpc/spec/openrpc.json @@ -1297,7 +1297,6 @@ "protocolVersion": "1", "featureFlags": { "accept_zklogin_in_multisig": false, - "advance_to_highest_supported_protocol_version": true, "authority_capabilities_v2": true, "bridge": false, "disable_invariant_violation_check_in_swap_loc": true, diff --git a/crates/iota-types/src/effects/effects_v1.rs b/crates/iota-types/src/effects/effects_v1.rs index ea26901b1a8..75210c4344b 100644 --- a/crates/iota-types/src/effects/effects_v1.rs +++ b/crates/iota-types/src/effects/effects_v1.rs @@ -2,24 +2,29 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::{ - collections::{BTreeMap, HashSet}, - fmt::{Display, Formatter, Write}, -}; +#[cfg(debug_assertions)] +use std::collections::HashSet; +use std::collections::{BTreeMap, BTreeSet}; use serde::{Deserialize, Serialize}; -use super::{IDOperation, ObjectChange}; +use super::{ + EffectsObjectChange, IDOperation, ObjectChange, + object_change::{ObjectIn, ObjectOut}, +}; +#[cfg(debug_assertions)] +use crate::is_system_package; use crate::{ base_types::{ - EpochId, IotaAddress, ObjectID, ObjectRef, SequenceNumber, TransactionDigest, - random_object_ref, + EpochId, IotaAddress, ObjectDigest, ObjectID, ObjectRef, SequenceNumber, TransactionDigest, + VersionDigest, }, - digests::{ObjectDigest, TransactionEventsDigest}, - effects::{InputSharedObject, TransactionEffectsAPI, UnchangedSharedKind}, + digests::{EffectsAuxDataDigest, TransactionEventsDigest}, + effects::{InputSharedObject, TransactionEffectsAPI}, + execution::SharedInput, execution_status::ExecutionStatus, gas::GasCostSummary, - object::Owner, + object::{OBJECT_START_VERSION, Owner}, }; /// The response from processing a transaction or a certified transaction @@ -30,102 +35,33 @@ pub struct TransactionEffectsV1 { /// The epoch when this transaction was executed. executed_epoch: EpochId, gas_used: GasCostSummary, - /// The version that every modified (mutated or deleted) object had before - /// it was modified by this transaction. - modified_at_versions: Vec<(ObjectID, SequenceNumber)>, - /// The object references of the shared objects used in this transaction. - /// Empty if no shared objects were used. - shared_objects: Vec, /// The transaction digest transaction_digest: TransactionDigest, - - // TODO: All the SequenceNumbers in the ObjectRefs below equal the same value (the lamport - // timestamp of the transaction). Consider factoring this out into one place in the effects. - /// ObjectRef and owner of new objects created. - created: Vec<(ObjectRef, Owner)>, - /// ObjectRef and owner of mutated objects, including gas object. - mutated: Vec<(ObjectRef, Owner)>, - /// ObjectRef and owner of objects that are unwrapped in this transaction. - /// Unwrapped objects are objects that were wrapped into other objects in - /// the past, and just got extracted out. - unwrapped: Vec<(ObjectRef, Owner)>, - /// Object Refs of objects now deleted (the new refs). - deleted: Vec, - /// Object refs of objects previously wrapped in other objects but now - /// deleted. - unwrapped_then_deleted: Vec, - /// Object refs of objects now wrapped in other objects. - wrapped: Vec, - /// The updated gas object reference. Have a dedicated field for convenient - /// access. It's also included in mutated. - gas_object: (ObjectRef, Owner), + /// The updated gas object reference, as an index into the `changed_objects` + /// vector. Having a dedicated field for convenient access. + /// System transaction that don't require gas will leave this as None. + gas_object_index: Option, /// The digest of the events emitted during execution, /// can be None if the transaction does not emit any event. events_digest: Option, /// The set of transaction digests this transaction depends on. dependencies: Vec, -} - -impl TransactionEffectsV1 { - pub fn new( - status: ExecutionStatus, - executed_epoch: EpochId, - gas_used: GasCostSummary, - modified_at_versions: Vec<(ObjectID, SequenceNumber)>, - shared_objects: Vec, - transaction_digest: TransactionDigest, - created: Vec<(ObjectRef, Owner)>, - mutated: Vec<(ObjectRef, Owner)>, - unwrapped: Vec<(ObjectRef, Owner)>, - deleted: Vec, - unwrapped_then_deleted: Vec, - wrapped: Vec, - gas_object: (ObjectRef, Owner), - events_digest: Option, - dependencies: Vec, - ) -> Self { - Self { - status, - executed_epoch, - gas_used, - modified_at_versions, - shared_objects, - transaction_digest, - created, - mutated, - unwrapped, - deleted, - unwrapped_then_deleted, - wrapped, - gas_object, - events_digest, - dependencies, - } - } - - pub fn modified_at_versions(&self) -> &[(ObjectID, SequenceNumber)] { - &self.modified_at_versions - } - - pub fn mutated(&self) -> &[(ObjectRef, Owner)] { - &self.mutated - } - - pub fn created(&self) -> &[(ObjectRef, Owner)] { - &self.created - } - pub fn unwrapped(&self) -> &[(ObjectRef, Owner)] { - &self.unwrapped - } - - pub fn deleted(&self) -> &[ObjectRef] { - &self.deleted - } - - pub fn wrapped(&self) -> &[ObjectRef] { - &self.wrapped - } + /// The version number of all the written Move objects by this transaction. + pub(crate) lamport_version: SequenceNumber, + /// Objects whose state are changed in the object store. + changed_objects: Vec<(ObjectID, EffectsObjectChange)>, + /// Shared objects that are not mutated in this transaction. Unlike owned + /// objects, read-only shared objects' version are not committed in the + /// transaction, and in order for a node to catch up and execute it + /// without consensus sequencing, the version needs to be committed in + /// the effects. + unchanged_shared_objects: Vec<(ObjectID, UnchangedSharedKind)>, + /// Auxiliary data that are not protocol-critical, generated as part of the + /// effects but are stored separately. Storing it separately allows us + /// to avoid bloating the effects with data that are not critical. + /// It also provides more flexibility on the format and type of the data. + aux_data_digest: Option, } impl TransactionEffectsAPI for TransactionEffectsV1 { @@ -142,127 +78,238 @@ impl TransactionEffectsAPI for TransactionEffectsV1 { } fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)> { - self.modified_at_versions.clone() + self.changed_objects + .iter() + .filter_map(|(id, change)| { + if let ObjectIn::Exist(((version, _digest), _owner)) = &change.input_state { + Some((*id, *version)) + } else { + None + } + }) + .collect() } fn lamport_version(&self) -> SequenceNumber { - SequenceNumber::lamport_increment(self.modified_at_versions.iter().map(|(_, v)| *v)) + self.lamport_version } fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)> { - unimplemented!("Only supposed by v2 and above"); + self.changed_objects + .iter() + .filter_map(|(id, change)| { + if let ObjectIn::Exist(((version, digest), owner)) = &change.input_state { + Some(((*id, *version, *digest), *owner)) + } else { + None + } + }) + .collect() } fn input_shared_objects(&self) -> Vec { - let modified: HashSet<_> = self.modified_at_versions.iter().map(|(r, _)| r).collect(); - self.shared_objects + self.changed_objects .iter() - .map(|r| { - if modified.contains(&r.0) { - InputSharedObject::Mutate(*r) - } else { - InputSharedObject::ReadOnly(*r) + .filter_map(|(id, change)| match &change.input_state { + ObjectIn::Exist(((version, digest), Owner::Shared { .. })) => { + Some(InputSharedObject::Mutate((*id, *version, *digest))) } + _ => None, }) + .chain( + self.unchanged_shared_objects + .iter() + .filter_map(|(id, change_kind)| match change_kind { + UnchangedSharedKind::ReadOnlyRoot((version, digest)) => { + Some(InputSharedObject::ReadOnly((*id, *version, *digest))) + } + UnchangedSharedKind::MutateDeleted(seqno) => { + Some(InputSharedObject::MutateDeleted(*id, *seqno)) + } + UnchangedSharedKind::ReadDeleted(seqno) => { + Some(InputSharedObject::ReadDeleted(*id, *seqno)) + } + UnchangedSharedKind::Cancelled(seqno) => { + Some(InputSharedObject::Cancelled(*id, *seqno)) + } + // We can not expose the per epoch config object as input shared object, + // since it does not require sequencing, and hence shall not be considered + // as a normal input shared object. + UnchangedSharedKind::PerEpochConfig => None, + }), + ) .collect() } fn created(&self) -> Vec<(ObjectRef, Owner)> { - self.created.clone() + self.changed_objects + .iter() + .filter_map(|(id, change)| { + match ( + &change.input_state, + &change.output_state, + &change.id_operation, + ) { + ( + ObjectIn::NotExist, + ObjectOut::ObjectWrite((digest, owner)), + IDOperation::Created, + ) => Some(((*id, self.lamport_version, *digest), *owner)), + ( + ObjectIn::NotExist, + ObjectOut::PackageWrite((version, digest)), + IDOperation::Created, + ) => Some(((*id, *version, *digest), Owner::Immutable)), + _ => None, + } + }) + .collect() } fn mutated(&self) -> Vec<(ObjectRef, Owner)> { - self.mutated.clone() + self.changed_objects + .iter() + .filter_map( + |(id, change)| match (&change.input_state, &change.output_state) { + (ObjectIn::Exist(_), ObjectOut::ObjectWrite((digest, owner))) => { + Some(((*id, self.lamport_version, *digest), *owner)) + } + (ObjectIn::Exist(_), ObjectOut::PackageWrite((version, digest))) => { + Some(((*id, *version, *digest), Owner::Immutable)) + } + _ => None, + }, + ) + .collect() } fn unwrapped(&self) -> Vec<(ObjectRef, Owner)> { - self.unwrapped.clone() + self.changed_objects + .iter() + .filter_map(|(id, change)| { + match ( + &change.input_state, + &change.output_state, + &change.id_operation, + ) { + ( + ObjectIn::NotExist, + ObjectOut::ObjectWrite((digest, owner)), + IDOperation::None, + ) => Some(((*id, self.lamport_version, *digest), *owner)), + _ => None, + } + }) + .collect() } fn deleted(&self) -> Vec { - self.deleted.clone() + self.changed_objects + .iter() + .filter_map(|(id, change)| { + match ( + &change.input_state, + &change.output_state, + &change.id_operation, + ) { + (ObjectIn::Exist(_), ObjectOut::NotExist, IDOperation::Deleted) => Some(( + *id, + self.lamport_version, + ObjectDigest::OBJECT_DIGEST_DELETED, + )), + _ => None, + } + }) + .collect() } fn unwrapped_then_deleted(&self) -> Vec { - self.unwrapped_then_deleted.clone() + self.changed_objects + .iter() + .filter_map(|(id, change)| { + match ( + &change.input_state, + &change.output_state, + &change.id_operation, + ) { + (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => Some(( + *id, + self.lamport_version, + ObjectDigest::OBJECT_DIGEST_DELETED, + )), + _ => None, + } + }) + .collect() } fn wrapped(&self) -> Vec { - self.wrapped.clone() + self.changed_objects + .iter() + .filter_map(|(id, change)| { + match ( + &change.input_state, + &change.output_state, + &change.id_operation, + ) { + (ObjectIn::Exist(_), ObjectOut::NotExist, IDOperation::None) => Some(( + *id, + self.lamport_version, + ObjectDigest::OBJECT_DIGEST_WRAPPED, + )), + _ => None, + } + }) + .collect() } fn object_changes(&self) -> Vec { - let modified_at: BTreeMap<_, _> = self.modified_at_versions.iter().copied().collect(); - - let created = self.created.iter().map(|((id, v, d), _)| ObjectChange { - id: *id, - input_version: None, - input_digest: None, - output_version: Some(*v), - output_digest: Some(*d), - id_operation: IDOperation::Created, - }); - - let mutated = self.mutated.iter().map(|((id, v, d), _)| ObjectChange { - id: *id, - input_version: modified_at.get(id).copied(), - input_digest: None, - output_version: Some(*v), - output_digest: Some(*d), - id_operation: IDOperation::None, - }); - - let unwrapped = self.unwrapped.iter().map(|((id, v, d), _)| ObjectChange { - id: *id, - input_version: None, - input_digest: None, - output_version: Some(*v), - output_digest: Some(*d), - id_operation: IDOperation::None, - }); + self.changed_objects + .iter() + .map(|(id, change)| { + let input_version_digest = match &change.input_state { + ObjectIn::NotExist => None, + ObjectIn::Exist((vd, _)) => Some(*vd), + }; + + let output_version_digest = match &change.output_state { + ObjectOut::NotExist => None, + ObjectOut::ObjectWrite((d, _)) => Some((self.lamport_version, *d)), + ObjectOut::PackageWrite(vd) => Some(*vd), + }; + + ObjectChange { + id: *id, - let deleted = self.deleted.iter().map(|(id, _, _)| ObjectChange { - id: *id, - input_version: modified_at.get(id).copied(), - input_digest: None, - output_version: None, - output_digest: None, - id_operation: IDOperation::Deleted, - }); + input_version: input_version_digest.map(|k| k.0), + input_digest: input_version_digest.map(|k| k.1), - let unwrapped_then_deleted = - self.unwrapped_then_deleted - .iter() - .map(|(id, _, _)| ObjectChange { - id: *id, - input_version: None, - input_digest: None, - output_version: None, - output_digest: None, - id_operation: IDOperation::Deleted, - }); - - let wrapped = self.wrapped.iter().map(|(id, _, _)| ObjectChange { - id: *id, - input_version: modified_at.get(id).copied(), - input_digest: None, - output_version: None, - output_digest: None, - id_operation: IDOperation::None, - }); + output_version: output_version_digest.map(|k| k.0), + output_digest: output_version_digest.map(|k| k.1), - created - .chain(mutated) - .chain(unwrapped) - .chain(deleted) - .chain(unwrapped_then_deleted) - .chain(wrapped) + id_operation: change.id_operation, + } + }) .collect() } fn gas_object(&self) -> (ObjectRef, Owner) { - self.gas_object + if let Some(gas_object_index) = self.gas_object_index { + let entry = &self.changed_objects[gas_object_index as usize]; + match entry.1.output_state { + ObjectOut::ObjectWrite((digest, owner)) => { + ((entry.0, self.lamport_version, digest), owner) + } + _ => panic!("Gas object must be an ObjectWrite in changed_objects"), + } + } else { + ( + (ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN), + Owner::AddressOwner(IotaAddress::default()), + ) + } } + fn events_digest(&self) -> Option<&TransactionEventsDigest> { self.events_digest.as_ref() } @@ -280,16 +327,7 @@ impl TransactionEffectsAPI for TransactionEffectsV1 { } fn unchanged_shared_objects(&self) -> Vec<(ObjectID, UnchangedSharedKind)> { - self.input_shared_objects() - .iter() - .filter_map(|o| match o { - // In effects v1, the only unchanged shared objects are read-only shared objects. - InputSharedObject::ReadOnly(oref) => { - Some((oref.0, UnchangedSharedKind::ReadOnlyRoot((oref.1, oref.2)))) - } - _ => None, - }) - .collect() + self.unchanged_shared_objects.clone() } fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus { @@ -311,96 +349,265 @@ impl TransactionEffectsAPI for TransactionEffectsV1 { fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject) { match kind { InputSharedObject::Mutate(obj_ref) => { - self.shared_objects.push(obj_ref); - self.modified_at_versions.push((obj_ref.0, obj_ref.1)); - } - InputSharedObject::ReadOnly(obj_ref) => { - self.shared_objects.push(obj_ref); - } - InputSharedObject::ReadDeleted(id, version) - | InputSharedObject::MutateDeleted(id, version) => { - self.shared_objects - .push((id, version, ObjectDigest::OBJECT_DIGEST_DELETED)); - } - InputSharedObject::Cancelled(..) => { - panic!("Transaction cancellation is not supported in effect v1"); + self.changed_objects.push((obj_ref.0, EffectsObjectChange { + input_state: ObjectIn::Exist(((obj_ref.1, obj_ref.2), Owner::Shared { + initial_shared_version: OBJECT_START_VERSION, + })), + output_state: ObjectOut::ObjectWrite((obj_ref.2, Owner::Shared { + initial_shared_version: obj_ref.1, + })), + id_operation: IDOperation::None, + })) } + InputSharedObject::ReadOnly(obj_ref) => self.unchanged_shared_objects.push(( + obj_ref.0, + UnchangedSharedKind::ReadOnlyRoot((obj_ref.1, obj_ref.2)), + )), + InputSharedObject::ReadDeleted(obj_id, seqno) => self + .unchanged_shared_objects + .push((obj_id, UnchangedSharedKind::ReadDeleted(seqno))), + InputSharedObject::MutateDeleted(obj_id, seqno) => self + .unchanged_shared_objects + .push((obj_id, UnchangedSharedKind::MutateDeleted(seqno))), + InputSharedObject::Cancelled(obj_id, seqno) => self + .unchanged_shared_objects + .push((obj_id, UnchangedSharedKind::Cancelled(seqno))), } } - fn unsafe_add_deleted_live_object_for_testing(&mut self, object: ObjectRef) { - self.modified_at_versions.push((object.0, object.1)); + fn unsafe_add_deleted_live_object_for_testing(&mut self, obj_ref: ObjectRef) { + self.changed_objects.push((obj_ref.0, EffectsObjectChange { + input_state: ObjectIn::Exist(( + (obj_ref.1, obj_ref.2), + Owner::AddressOwner(IotaAddress::default()), + )), + output_state: ObjectOut::ObjectWrite(( + obj_ref.2, + Owner::AddressOwner(IotaAddress::default()), + )), + id_operation: IDOperation::None, + })) } - fn unsafe_add_object_tombstone_for_testing(&mut self, object: ObjectRef) { - self.deleted.push(object); + fn unsafe_add_object_tombstone_for_testing(&mut self, obj_ref: ObjectRef) { + self.changed_objects.push((obj_ref.0, EffectsObjectChange { + input_state: ObjectIn::Exist(( + (obj_ref.1, obj_ref.2), + Owner::AddressOwner(IotaAddress::default()), + )), + output_state: ObjectOut::NotExist, + id_operation: IDOperation::Deleted, + })) } } -impl Display for TransactionEffectsV1 { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let mut writer = String::new(); - writeln!(writer, "Status : {:?}", self.status)?; - if !self.created.is_empty() { - writeln!(writer, "Created Objects:")?; - for ((id, _, _), owner) in &self.created { - writeln!(writer, " - ID: {} , Owner: {}", id, owner)?; - } - } - if !self.mutated.is_empty() { - writeln!(writer, "Mutated Objects:")?; - for ((id, _, _), owner) in &self.mutated { - writeln!(writer, " - ID: {} , Owner: {}", id, owner)?; - } - } - if !self.deleted.is_empty() { - writeln!(writer, "Deleted Objects:")?; - for (id, _, _) in &self.deleted { - writeln!(writer, " - ID: {}", id)?; - } - } - if !self.wrapped.is_empty() { - writeln!(writer, "Wrapped Objects:")?; - for (id, _, _) in &self.wrapped { - writeln!(writer, " - ID: {}", id)?; +impl TransactionEffectsV1 { + pub fn new( + status: ExecutionStatus, + executed_epoch: EpochId, + gas_used: GasCostSummary, + shared_objects: Vec, + loaded_per_epoch_config_objects: BTreeSet, + transaction_digest: TransactionDigest, + lamport_version: SequenceNumber, + changed_objects: BTreeMap, + gas_object: Option, + events_digest: Option, + dependencies: Vec, + ) -> Self { + let unchanged_shared_objects = shared_objects + .into_iter() + .filter_map(|shared_input| match shared_input { + SharedInput::Existing((id, version, digest)) => { + if changed_objects.contains_key(&id) { + None + } else { + Some((id, UnchangedSharedKind::ReadOnlyRoot((version, digest)))) + } + } + SharedInput::Deleted((id, version, mutable, _)) => { + debug_assert!(!changed_objects.contains_key(&id)); + if mutable { + Some((id, UnchangedSharedKind::MutateDeleted(version))) + } else { + Some((id, UnchangedSharedKind::ReadDeleted(version))) + } + } + SharedInput::Cancelled((id, version)) => { + debug_assert!(!changed_objects.contains_key(&id)); + Some((id, UnchangedSharedKind::Cancelled(version))) + } + }) + .chain( + loaded_per_epoch_config_objects + .into_iter() + .map(|id| (id, UnchangedSharedKind::PerEpochConfig)), + ) + .collect(); + let changed_objects: Vec<_> = changed_objects.into_iter().collect(); + + let gas_object_index = gas_object.map(|gas_id| { + changed_objects + .iter() + .position(|(id, _)| id == &gas_id) + .unwrap() as u32 + }); + + #[allow(clippy::let_and_return)] + let result = Self { + status, + executed_epoch, + gas_used, + transaction_digest, + lamport_version, + changed_objects, + unchanged_shared_objects, + gas_object_index, + events_digest, + dependencies, + aux_data_digest: None, + }; + #[cfg(debug_assertions)] + result.check_invariant(); + + result + } + + /// This function demonstrates what's the invariant of the effects. + /// It also documents the semantics of different combinations in object + /// changes. + #[cfg(debug_assertions)] + fn check_invariant(&self) { + let mut unique_ids = HashSet::new(); + for (id, change) in &self.changed_objects { + assert!(unique_ids.insert(*id)); + match ( + &change.input_state, + &change.output_state, + &change.id_operation, + ) { + (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Created) => { + // created and then wrapped Move object. + } + (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => { + // unwrapped and then deleted Move object. + } + (ObjectIn::NotExist, ObjectOut::ObjectWrite((_, owner)), IDOperation::None) => { + // unwrapped Move object. + // It's not allowed to make an object shared after unwrapping. + assert!(!owner.is_shared()); + } + (ObjectIn::NotExist, ObjectOut::ObjectWrite(..), IDOperation::Created) => { + // created Move object. + } + (ObjectIn::NotExist, ObjectOut::PackageWrite(_), IDOperation::Created) => { + // created Move package or user Move package upgrade. + } + ( + ObjectIn::Exist(((old_version, _), old_owner)), + ObjectOut::NotExist, + IDOperation::None, + ) => { + // wrapped. + assert!(old_version.value() < self.lamport_version.value()); + assert!( + !old_owner.is_shared() && !old_owner.is_immutable(), + "Cannot wrap shared or immutable object" + ); + } + ( + ObjectIn::Exist(((old_version, _), old_owner)), + ObjectOut::NotExist, + IDOperation::Deleted, + ) => { + // deleted. + assert!(old_version.value() < self.lamport_version.value()); + assert!(!old_owner.is_immutable(), "Cannot delete immutable object"); + } + ( + ObjectIn::Exist(((old_version, old_digest), old_owner)), + ObjectOut::ObjectWrite((new_digest, new_owner)), + IDOperation::None, + ) => { + // mutated. + assert!(old_version.value() < self.lamport_version.value()); + assert_ne!(old_digest, new_digest); + assert!(!old_owner.is_immutable(), "Cannot mutate immutable object"); + if old_owner.is_shared() { + assert!(new_owner.is_shared(), "Cannot un-share an object"); + } else { + assert!(!new_owner.is_shared(), "Cannot share an existing object"); + } + } + ( + ObjectIn::Exist(((old_version, old_digest), old_owner)), + ObjectOut::PackageWrite((new_version, new_digest)), + IDOperation::None, + ) => { + // system package upgrade. + assert!( + old_owner.is_immutable() && is_system_package(*id), + "Must be a system package" + ); + assert_eq!(old_version.value() + 1, new_version.value()); + assert_ne!(old_digest, new_digest); + } + _ => { + panic!("Impossible object change: {:?}, {:?}", id, change); + } } } - if !self.unwrapped.is_empty() { - writeln!(writer, "Unwrapped Objects:")?; - for ((id, _, _), owner) in &self.unwrapped { - writeln!(writer, " - ID: {} , Owner: {}", id, owner)?; - } + // Make sure that gas object exists in changed_objects. + let (_, owner) = self.gas_object(); + assert!(matches!(owner, Owner::AddressOwner(_))); + + for (id, _) in &self.unchanged_shared_objects { + assert!( + unique_ids.insert(*id), + "Duplicate object id: {:?}\n{:#?}", + id, + self + ); } - write!(f, "{}", writer) + } + + pub fn changed_objects(&self) -> &[(ObjectID, EffectsObjectChange)] { + &self.changed_objects } } impl Default for TransactionEffectsV1 { fn default() -> Self { - TransactionEffectsV1 { + Self { status: ExecutionStatus::Success, executed_epoch: 0, - gas_used: GasCostSummary { - computation_cost: 0, - storage_cost: 0, - storage_rebate: 0, - non_refundable_storage_fee: 0, - }, - modified_at_versions: Vec::new(), - shared_objects: Vec::new(), - transaction_digest: TransactionDigest::random(), - created: Vec::new(), - mutated: Vec::new(), - unwrapped: Vec::new(), - deleted: Vec::new(), - unwrapped_then_deleted: Vec::new(), - wrapped: Vec::new(), - gas_object: ( - random_object_ref(), - Owner::AddressOwner(IotaAddress::default()), - ), + gas_used: GasCostSummary::default(), + transaction_digest: TransactionDigest::default(), + lamport_version: SequenceNumber::default(), + changed_objects: vec![], + unchanged_shared_objects: vec![], + gas_object_index: None, events_digest: None, - dependencies: Vec::new(), + dependencies: vec![], + aux_data_digest: None, } } } + +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum UnchangedSharedKind { + /// Read-only shared objects from the input. We don't really need + /// ObjectDigest for protocol correctness, but it will make it easier to + /// verify untrusted read. + ReadOnlyRoot(VersionDigest), + /// Deleted shared objects that appear mutably/owned in the input. + MutateDeleted(SequenceNumber), + /// Deleted shared objects that appear as read-only in the input. + ReadDeleted(SequenceNumber), + /// Shared objects in cancelled transaction. The sequence number embed + /// cancellation reason. + Cancelled(SequenceNumber), + /// Read of a per-epoch config object that should remain the same during an + /// epoch. + PerEpochConfig, +} diff --git a/crates/iota-types/src/effects/effects_v2.rs b/crates/iota-types/src/effects/effects_v2.rs deleted file mode 100644 index 68184453467..00000000000 --- a/crates/iota-types/src/effects/effects_v2.rs +++ /dev/null @@ -1,613 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// Modifications Copyright (c) 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(debug_assertions)] -use std::collections::HashSet; -use std::collections::{BTreeMap, BTreeSet}; - -use serde::{Deserialize, Serialize}; - -use super::{ - EffectsObjectChange, IDOperation, ObjectChange, - object_change::{ObjectIn, ObjectOut}, -}; -#[cfg(debug_assertions)] -use crate::is_system_package; -use crate::{ - base_types::{ - EpochId, IotaAddress, ObjectDigest, ObjectID, ObjectRef, SequenceNumber, TransactionDigest, - VersionDigest, - }, - digests::{EffectsAuxDataDigest, TransactionEventsDigest}, - effects::{InputSharedObject, TransactionEffectsAPI}, - execution::SharedInput, - execution_status::ExecutionStatus, - gas::GasCostSummary, - object::{OBJECT_START_VERSION, Owner}, -}; - -/// The response from processing a transaction or a certified transaction -#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] -pub struct TransactionEffectsV2 { - /// The status of the execution - status: ExecutionStatus, - /// The epoch when this transaction was executed. - executed_epoch: EpochId, - gas_used: GasCostSummary, - /// The transaction digest - transaction_digest: TransactionDigest, - /// The updated gas object reference, as an index into the `changed_objects` - /// vector. Having a dedicated field for convenient access. - /// System transaction that don't require gas will leave this as None. - gas_object_index: Option, - /// The digest of the events emitted during execution, - /// can be None if the transaction does not emit any event. - events_digest: Option, - /// The set of transaction digests this transaction depends on. - dependencies: Vec, - - /// The version number of all the written Move objects by this transaction. - pub(crate) lamport_version: SequenceNumber, - /// Objects whose state are changed in the object store. - changed_objects: Vec<(ObjectID, EffectsObjectChange)>, - /// Shared objects that are not mutated in this transaction. Unlike owned - /// objects, read-only shared objects' version are not committed in the - /// transaction, and in order for a node to catch up and execute it - /// without consensus sequencing, the version needs to be committed in - /// the effects. - unchanged_shared_objects: Vec<(ObjectID, UnchangedSharedKind)>, - /// Auxiliary data that are not protocol-critical, generated as part of the - /// effects but are stored separately. Storing it separately allows us - /// to avoid bloating the effects with data that are not critical. - /// It also provides more flexibility on the format and type of the data. - aux_data_digest: Option, -} - -impl TransactionEffectsAPI for TransactionEffectsV2 { - fn status(&self) -> &ExecutionStatus { - &self.status - } - - fn into_status(self) -> ExecutionStatus { - self.status - } - - fn executed_epoch(&self) -> EpochId { - self.executed_epoch - } - - fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)> { - self.changed_objects - .iter() - .filter_map(|(id, change)| { - if let ObjectIn::Exist(((version, _digest), _owner)) = &change.input_state { - Some((*id, *version)) - } else { - None - } - }) - .collect() - } - - fn lamport_version(&self) -> SequenceNumber { - self.lamport_version - } - - fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)> { - self.changed_objects - .iter() - .filter_map(|(id, change)| { - if let ObjectIn::Exist(((version, digest), owner)) = &change.input_state { - Some(((*id, *version, *digest), *owner)) - } else { - None - } - }) - .collect() - } - - fn input_shared_objects(&self) -> Vec { - self.changed_objects - .iter() - .filter_map(|(id, change)| match &change.input_state { - ObjectIn::Exist(((version, digest), Owner::Shared { .. })) => { - Some(InputSharedObject::Mutate((*id, *version, *digest))) - } - _ => None, - }) - .chain( - self.unchanged_shared_objects - .iter() - .filter_map(|(id, change_kind)| match change_kind { - UnchangedSharedKind::ReadOnlyRoot((version, digest)) => { - Some(InputSharedObject::ReadOnly((*id, *version, *digest))) - } - UnchangedSharedKind::MutateDeleted(seqno) => { - Some(InputSharedObject::MutateDeleted(*id, *seqno)) - } - UnchangedSharedKind::ReadDeleted(seqno) => { - Some(InputSharedObject::ReadDeleted(*id, *seqno)) - } - UnchangedSharedKind::Cancelled(seqno) => { - Some(InputSharedObject::Cancelled(*id, *seqno)) - } - // We can not expose the per epoch config object as input shared object, - // since it does not require sequencing, and hence shall not be considered - // as a normal input shared object. - UnchangedSharedKind::PerEpochConfig => None, - }), - ) - .collect() - } - - fn created(&self) -> Vec<(ObjectRef, Owner)> { - self.changed_objects - .iter() - .filter_map(|(id, change)| { - match ( - &change.input_state, - &change.output_state, - &change.id_operation, - ) { - ( - ObjectIn::NotExist, - ObjectOut::ObjectWrite((digest, owner)), - IDOperation::Created, - ) => Some(((*id, self.lamport_version, *digest), *owner)), - ( - ObjectIn::NotExist, - ObjectOut::PackageWrite((version, digest)), - IDOperation::Created, - ) => Some(((*id, *version, *digest), Owner::Immutable)), - _ => None, - } - }) - .collect() - } - - fn mutated(&self) -> Vec<(ObjectRef, Owner)> { - self.changed_objects - .iter() - .filter_map( - |(id, change)| match (&change.input_state, &change.output_state) { - (ObjectIn::Exist(_), ObjectOut::ObjectWrite((digest, owner))) => { - Some(((*id, self.lamport_version, *digest), *owner)) - } - (ObjectIn::Exist(_), ObjectOut::PackageWrite((version, digest))) => { - Some(((*id, *version, *digest), Owner::Immutable)) - } - _ => None, - }, - ) - .collect() - } - - fn unwrapped(&self) -> Vec<(ObjectRef, Owner)> { - self.changed_objects - .iter() - .filter_map(|(id, change)| { - match ( - &change.input_state, - &change.output_state, - &change.id_operation, - ) { - ( - ObjectIn::NotExist, - ObjectOut::ObjectWrite((digest, owner)), - IDOperation::None, - ) => Some(((*id, self.lamport_version, *digest), *owner)), - _ => None, - } - }) - .collect() - } - - fn deleted(&self) -> Vec { - self.changed_objects - .iter() - .filter_map(|(id, change)| { - match ( - &change.input_state, - &change.output_state, - &change.id_operation, - ) { - (ObjectIn::Exist(_), ObjectOut::NotExist, IDOperation::Deleted) => Some(( - *id, - self.lamport_version, - ObjectDigest::OBJECT_DIGEST_DELETED, - )), - _ => None, - } - }) - .collect() - } - - fn unwrapped_then_deleted(&self) -> Vec { - self.changed_objects - .iter() - .filter_map(|(id, change)| { - match ( - &change.input_state, - &change.output_state, - &change.id_operation, - ) { - (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => Some(( - *id, - self.lamport_version, - ObjectDigest::OBJECT_DIGEST_DELETED, - )), - _ => None, - } - }) - .collect() - } - - fn wrapped(&self) -> Vec { - self.changed_objects - .iter() - .filter_map(|(id, change)| { - match ( - &change.input_state, - &change.output_state, - &change.id_operation, - ) { - (ObjectIn::Exist(_), ObjectOut::NotExist, IDOperation::None) => Some(( - *id, - self.lamport_version, - ObjectDigest::OBJECT_DIGEST_WRAPPED, - )), - _ => None, - } - }) - .collect() - } - - fn object_changes(&self) -> Vec { - self.changed_objects - .iter() - .map(|(id, change)| { - let input_version_digest = match &change.input_state { - ObjectIn::NotExist => None, - ObjectIn::Exist((vd, _)) => Some(*vd), - }; - - let output_version_digest = match &change.output_state { - ObjectOut::NotExist => None, - ObjectOut::ObjectWrite((d, _)) => Some((self.lamport_version, *d)), - ObjectOut::PackageWrite(vd) => Some(*vd), - }; - - ObjectChange { - id: *id, - - input_version: input_version_digest.map(|k| k.0), - input_digest: input_version_digest.map(|k| k.1), - - output_version: output_version_digest.map(|k| k.0), - output_digest: output_version_digest.map(|k| k.1), - - id_operation: change.id_operation, - } - }) - .collect() - } - - fn gas_object(&self) -> (ObjectRef, Owner) { - if let Some(gas_object_index) = self.gas_object_index { - let entry = &self.changed_objects[gas_object_index as usize]; - match entry.1.output_state { - ObjectOut::ObjectWrite((digest, owner)) => { - ((entry.0, self.lamport_version, digest), owner) - } - _ => panic!("Gas object must be an ObjectWrite in changed_objects"), - } - } else { - ( - (ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN), - Owner::AddressOwner(IotaAddress::default()), - ) - } - } - - fn events_digest(&self) -> Option<&TransactionEventsDigest> { - self.events_digest.as_ref() - } - - fn dependencies(&self) -> &[TransactionDigest] { - &self.dependencies - } - - fn transaction_digest(&self) -> &TransactionDigest { - &self.transaction_digest - } - - fn gas_cost_summary(&self) -> &GasCostSummary { - &self.gas_used - } - - fn unchanged_shared_objects(&self) -> Vec<(ObjectID, UnchangedSharedKind)> { - self.unchanged_shared_objects.clone() - } - - fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus { - &mut self.status - } - - fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary { - &mut self.gas_used - } - - fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest { - &mut self.transaction_digest - } - - fn dependencies_mut_for_testing(&mut self) -> &mut Vec { - &mut self.dependencies - } - - fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject) { - match kind { - InputSharedObject::Mutate(obj_ref) => { - self.changed_objects.push((obj_ref.0, EffectsObjectChange { - input_state: ObjectIn::Exist(((obj_ref.1, obj_ref.2), Owner::Shared { - initial_shared_version: OBJECT_START_VERSION, - })), - output_state: ObjectOut::ObjectWrite((obj_ref.2, Owner::Shared { - initial_shared_version: obj_ref.1, - })), - id_operation: IDOperation::None, - })) - } - InputSharedObject::ReadOnly(obj_ref) => self.unchanged_shared_objects.push(( - obj_ref.0, - UnchangedSharedKind::ReadOnlyRoot((obj_ref.1, obj_ref.2)), - )), - InputSharedObject::ReadDeleted(obj_id, seqno) => self - .unchanged_shared_objects - .push((obj_id, UnchangedSharedKind::ReadDeleted(seqno))), - InputSharedObject::MutateDeleted(obj_id, seqno) => self - .unchanged_shared_objects - .push((obj_id, UnchangedSharedKind::MutateDeleted(seqno))), - InputSharedObject::Cancelled(obj_id, seqno) => self - .unchanged_shared_objects - .push((obj_id, UnchangedSharedKind::Cancelled(seqno))), - } - } - - fn unsafe_add_deleted_live_object_for_testing(&mut self, obj_ref: ObjectRef) { - self.changed_objects.push((obj_ref.0, EffectsObjectChange { - input_state: ObjectIn::Exist(( - (obj_ref.1, obj_ref.2), - Owner::AddressOwner(IotaAddress::default()), - )), - output_state: ObjectOut::ObjectWrite(( - obj_ref.2, - Owner::AddressOwner(IotaAddress::default()), - )), - id_operation: IDOperation::None, - })) - } - - fn unsafe_add_object_tombstone_for_testing(&mut self, obj_ref: ObjectRef) { - self.changed_objects.push((obj_ref.0, EffectsObjectChange { - input_state: ObjectIn::Exist(( - (obj_ref.1, obj_ref.2), - Owner::AddressOwner(IotaAddress::default()), - )), - output_state: ObjectOut::NotExist, - id_operation: IDOperation::Deleted, - })) - } -} - -impl TransactionEffectsV2 { - pub fn new( - status: ExecutionStatus, - executed_epoch: EpochId, - gas_used: GasCostSummary, - shared_objects: Vec, - loaded_per_epoch_config_objects: BTreeSet, - transaction_digest: TransactionDigest, - lamport_version: SequenceNumber, - changed_objects: BTreeMap, - gas_object: Option, - events_digest: Option, - dependencies: Vec, - ) -> Self { - let unchanged_shared_objects = shared_objects - .into_iter() - .filter_map(|shared_input| match shared_input { - SharedInput::Existing((id, version, digest)) => { - if changed_objects.contains_key(&id) { - None - } else { - Some((id, UnchangedSharedKind::ReadOnlyRoot((version, digest)))) - } - } - SharedInput::Deleted((id, version, mutable, _)) => { - debug_assert!(!changed_objects.contains_key(&id)); - if mutable { - Some((id, UnchangedSharedKind::MutateDeleted(version))) - } else { - Some((id, UnchangedSharedKind::ReadDeleted(version))) - } - } - SharedInput::Cancelled((id, version)) => { - debug_assert!(!changed_objects.contains_key(&id)); - Some((id, UnchangedSharedKind::Cancelled(version))) - } - }) - .chain( - loaded_per_epoch_config_objects - .into_iter() - .map(|id| (id, UnchangedSharedKind::PerEpochConfig)), - ) - .collect(); - let changed_objects: Vec<_> = changed_objects.into_iter().collect(); - - let gas_object_index = gas_object.map(|gas_id| { - changed_objects - .iter() - .position(|(id, _)| id == &gas_id) - .unwrap() as u32 - }); - - #[allow(clippy::let_and_return)] - let result = Self { - status, - executed_epoch, - gas_used, - transaction_digest, - lamport_version, - changed_objects, - unchanged_shared_objects, - gas_object_index, - events_digest, - dependencies, - aux_data_digest: None, - }; - #[cfg(debug_assertions)] - result.check_invariant(); - - result - } - - /// This function demonstrates what's the invariant of the effects. - /// It also documents the semantics of different combinations in object - /// changes. - #[cfg(debug_assertions)] - fn check_invariant(&self) { - let mut unique_ids = HashSet::new(); - for (id, change) in &self.changed_objects { - assert!(unique_ids.insert(*id)); - match ( - &change.input_state, - &change.output_state, - &change.id_operation, - ) { - (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Created) => { - // created and then wrapped Move object. - } - (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => { - // unwrapped and then deleted Move object. - } - (ObjectIn::NotExist, ObjectOut::ObjectWrite((_, owner)), IDOperation::None) => { - // unwrapped Move object. - // It's not allowed to make an object shared after unwrapping. - assert!(!owner.is_shared()); - } - (ObjectIn::NotExist, ObjectOut::ObjectWrite(..), IDOperation::Created) => { - // created Move object. - } - (ObjectIn::NotExist, ObjectOut::PackageWrite(_), IDOperation::Created) => { - // created Move package or user Move package upgrade. - } - ( - ObjectIn::Exist(((old_version, _), old_owner)), - ObjectOut::NotExist, - IDOperation::None, - ) => { - // wrapped. - assert!(old_version.value() < self.lamport_version.value()); - assert!( - !old_owner.is_shared() && !old_owner.is_immutable(), - "Cannot wrap shared or immutable object" - ); - } - ( - ObjectIn::Exist(((old_version, _), old_owner)), - ObjectOut::NotExist, - IDOperation::Deleted, - ) => { - // deleted. - assert!(old_version.value() < self.lamport_version.value()); - assert!(!old_owner.is_immutable(), "Cannot delete immutable object"); - } - ( - ObjectIn::Exist(((old_version, old_digest), old_owner)), - ObjectOut::ObjectWrite((new_digest, new_owner)), - IDOperation::None, - ) => { - // mutated. - assert!(old_version.value() < self.lamport_version.value()); - assert_ne!(old_digest, new_digest); - assert!(!old_owner.is_immutable(), "Cannot mutate immutable object"); - if old_owner.is_shared() { - assert!(new_owner.is_shared(), "Cannot un-share an object"); - } else { - assert!(!new_owner.is_shared(), "Cannot share an existing object"); - } - } - ( - ObjectIn::Exist(((old_version, old_digest), old_owner)), - ObjectOut::PackageWrite((new_version, new_digest)), - IDOperation::None, - ) => { - // system package upgrade. - assert!( - old_owner.is_immutable() && is_system_package(*id), - "Must be a system package" - ); - assert_eq!(old_version.value() + 1, new_version.value()); - assert_ne!(old_digest, new_digest); - } - _ => { - panic!("Impossible object change: {:?}, {:?}", id, change); - } - } - } - // Make sure that gas object exists in changed_objects. - let (_, owner) = self.gas_object(); - assert!(matches!(owner, Owner::AddressOwner(_))); - - for (id, _) in &self.unchanged_shared_objects { - assert!( - unique_ids.insert(*id), - "Duplicate object id: {:?}\n{:#?}", - id, - self - ); - } - } - - pub fn changed_objects(&self) -> &[(ObjectID, EffectsObjectChange)] { - &self.changed_objects - } -} - -impl Default for TransactionEffectsV2 { - fn default() -> Self { - Self { - status: ExecutionStatus::Success, - executed_epoch: 0, - gas_used: GasCostSummary::default(), - transaction_digest: TransactionDigest::default(), - lamport_version: SequenceNumber::default(), - changed_objects: vec![], - unchanged_shared_objects: vec![], - gas_object_index: None, - events_digest: None, - dependencies: vec![], - aux_data_digest: None, - } - } -} - -#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum UnchangedSharedKind { - /// Read-only shared objects from the input. We don't really need - /// ObjectDigest for protocol correctness, but it will make it easier to - /// verify untrusted read. - ReadOnlyRoot(VersionDigest), - /// Deleted shared objects that appear mutably/owned in the input. - MutateDeleted(SequenceNumber), - /// Deleted shared objects that appear as read-only in the input. - ReadDeleted(SequenceNumber), - /// Shared objects in cancelled transaction. The sequence number embed - /// cancellation reason. - Cancelled(SequenceNumber), - /// Read of a per-epoch config object that should remain the same during an - /// epoch. - PerEpochConfig, -} diff --git a/crates/iota-types/src/effects/mod.rs b/crates/iota-types/src/effects/mod.rs index 8d9b507562c..24f7f528259 100644 --- a/crates/iota-types/src/effects/mod.rs +++ b/crates/iota-types/src/effects/mod.rs @@ -5,14 +5,13 @@ use std::collections::{BTreeMap, BTreeSet}; use effects_v1::TransactionEffectsV1; -pub use effects_v2::UnchangedSharedKind; +pub use effects_v1::UnchangedSharedKind; use enum_dispatch::enum_dispatch; pub use object_change::{EffectsObjectChange, ObjectIn, ObjectOut}; use serde::{Deserialize, Serialize}; use shared_crypto::intent::{Intent, IntentScope}; pub use test_effects_builder::TestEffectsBuilder; -use self::effects_v2::TransactionEffectsV2; use crate::{ base_types::{ExecutionDigests, ObjectID, ObjectRef, SequenceNumber}, committee::{Committee, EpochId}, @@ -32,7 +31,6 @@ use crate::{ }; mod effects_v1; -mod effects_v2; mod object_change; mod test_effects_builder; @@ -59,7 +57,6 @@ pub const APPROX_SIZE_OF_OWNER: usize = 48; #[allow(clippy::large_enum_variant)] pub enum TransactionEffects { V1(TransactionEffectsV1), - V2(TransactionEffectsV2), } impl Message for TransactionEffects { @@ -74,7 +71,7 @@ impl Message for TransactionEffects { // TODO: Get rid of this and use TestEffectsBuilder instead. impl Default for TransactionEffects { fn default() -> Self { - TransactionEffects::V2(Default::default()) + TransactionEffects::V1(Default::default()) } } @@ -87,44 +84,6 @@ impl TransactionEffects { /// Creates a TransactionEffects message from the results of execution, /// choosing the correct format for the current protocol version. pub fn new_from_execution_v1( - status: ExecutionStatus, - executed_epoch: EpochId, - gas_used: GasCostSummary, - modified_at_versions: Vec<(ObjectID, SequenceNumber)>, - shared_objects: Vec, - transaction_digest: TransactionDigest, - created: Vec<(ObjectRef, Owner)>, - mutated: Vec<(ObjectRef, Owner)>, - unwrapped: Vec<(ObjectRef, Owner)>, - deleted: Vec, - unwrapped_then_deleted: Vec, - wrapped: Vec, - gas_object: (ObjectRef, Owner), - events_digest: Option, - dependencies: Vec, - ) -> Self { - Self::V1(TransactionEffectsV1::new( - status, - executed_epoch, - gas_used, - modified_at_versions, - shared_objects, - transaction_digest, - created, - mutated, - unwrapped, - deleted, - unwrapped_then_deleted, - wrapped, - gas_object, - events_digest, - dependencies, - )) - } - - /// Creates a TransactionEffects message from the results of execution, - /// choosing the correct format for the current protocol version. - pub fn new_from_execution_v2( status: ExecutionStatus, executed_epoch: EpochId, gas_used: GasCostSummary, @@ -137,7 +96,7 @@ impl TransactionEffects { events_digest: Option, dependencies: Vec, ) -> Self { - Self::V2(TransactionEffectsV2::new( + Self::V1(TransactionEffectsV1::new( status, executed_epoch, gas_used, @@ -160,30 +119,6 @@ impl TransactionEffects { } pub fn estimate_effects_size_upperbound_v1( - num_writes: usize, - num_mutables: usize, - num_deletes: usize, - num_deps: usize, - ) -> usize { - let fixed_sizes = APPROX_SIZE_OF_EXECUTION_STATUS - + APPROX_SIZE_OF_EPOCH_ID - + APPROX_SIZE_OF_GAS_COST_SUMMARY - + APPROX_SIZE_OF_OPT_TX_EVENTS_DIGEST; - - // Each write or delete contributes at roughly this amount because: - // Each write can be a mutation which can show up in `mutated` and - // `modified_at_versions` `num_delete` is added for padding - let approx_change_entry_size = 1_000 - + (APPROX_SIZE_OF_OWNER + APPROX_SIZE_OF_OBJECT_REF) * num_writes - + (APPROX_SIZE_OF_OBJECT_REF * num_mutables) - + (APPROX_SIZE_OF_OBJECT_REF * num_deletes); - - let deps_size = 1_000 + APPROX_SIZE_OF_TX_DIGEST * num_deps; - - fixed_sizes + approx_change_entry_size + deps_size - } - - pub fn estimate_effects_size_upperbound_v2( num_writes: usize, num_modifies: usize, num_deps: usize, @@ -317,7 +252,7 @@ pub trait TransactionEffectsAPI { /// Metadata of objects prior to modification. This includes any object that /// exists in the store prior to this transaction and is modified in /// this transaction. It includes objects that are mutated, wrapped and - /// deleted. This API is only available on effects v2 and above. + /// deleted. fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)>; /// Returns the list of sequenced shared objects used in the input. /// This is needed in effects because in transaction we only have object ID diff --git a/crates/iota-types/src/effects/test_effects_builder.rs b/crates/iota-types/src/effects/test_effects_builder.rs index 8ddf8d51717..4bc63afdd88 100644 --- a/crates/iota-types/src/effects/test_effects_builder.rs +++ b/crates/iota-types/src/effects/test_effects_builder.rs @@ -134,7 +134,7 @@ impl TestEffectsBuilder { let gas_object_id = self.transaction.transaction_data().gas()[0].0; let event_digest = self.events_digest; let dependencies = vec![]; - TransactionEffects::new_from_execution_v2( + TransactionEffects::new_from_execution_v1( status, executed_epoch, GasCostSummary::default(), diff --git a/crates/iota-types/src/full_checkpoint_content.rs b/crates/iota-types/src/full_checkpoint_content.rs index cd8c5bbf299..db44a808589 100644 --- a/crates/iota-types/src/full_checkpoint_content.rs +++ b/crates/iota-types/src/full_checkpoint_content.rs @@ -4,7 +4,6 @@ use std::collections::BTreeMap; -use itertools::Either; use serde::{Deserialize, Serialize}; use tap::Pipe; @@ -95,25 +94,8 @@ impl CheckpointTransaction { pub fn removed_objects_pre_version(&self) -> impl Iterator { // Iterator over id and versions for all deleted or wrapped objects match &self.effects { - TransactionEffects::V1(v1) => Either::Left( - // Effects v1 has deleted and wrapped objects versions as the "new" version, not - // the old one that was actually removed. So we need to take these - // and then look them up in the `modified_at_versions`. - // No need to chain unwrapped_then_deleted because these objects must have been - // wrapped before the transaction, hence they will not be in - // modified_at_versions / input_objects. - v1.deleted().iter().chain(v1.wrapped()).map(|(id, _, _)| { - // lookup the old version for mutated objects - let (_, old_version) = v1 - .modified_at_versions() - .iter() - .find(|(oid, _old_version)| oid == id) - .expect("deleted/wrapped object must have entry in 'modified_at_versions'"); - (id, old_version) - }), - ), - TransactionEffects::V2(v2) => { - Either::Right(v2.changed_objects().iter().filter_map(|(id, change)| { + TransactionEffects::V1(v1) => { + v1.changed_objects().iter().filter_map(|(id, change)| { match ( &change.input_state, &change.output_state, @@ -134,7 +116,7 @@ impl CheckpointTransaction { ) => Some((id, version)), _ => None, } - })) + }) } } // Use id and version to lookup in input Objects @@ -156,24 +138,8 @@ impl CheckpointTransaction { pub fn changed_objects(&self) -> impl Iterator)> { // Iterator over ((ObjectId, new version), Option) match &self.effects { - TransactionEffects::V1(v1) => Either::Left( - v1.created() - .iter() - .chain(v1.unwrapped()) - .map(|((id, version, _), _)| ((id, version), None)) - .chain(v1.mutated().iter().map(|((id, version, _), _)| { - // lookup the old version for mutated objects - let (_, old_version) = v1 - .modified_at_versions() - .iter() - .find(|(oid, _old_version)| oid == id) - .expect("mutated object must have entry in modified_at_versions"); - - ((id, version), Some(old_version)) - })), - ), - TransactionEffects::V2(v2) => { - Either::Right(v2.changed_objects().iter().filter_map(|(id, change)| { + TransactionEffects::V1(v1) => { + v1.changed_objects().iter().filter_map(|(id, change)| { match ( &change.input_state, &change.output_state, @@ -181,7 +147,7 @@ impl CheckpointTransaction { ) { // Created Objects (ObjectIn::NotExist, ObjectOut::ObjectWrite(_), IDOperation::Created) => { - Some(((id, &v2.lamport_version), None)) + Some(((id, &v1.lamport_version), None)) } ( ObjectIn::NotExist, @@ -191,12 +157,12 @@ impl CheckpointTransaction { // Unwrapped Objects (ObjectIn::NotExist, ObjectOut::ObjectWrite(_), IDOperation::None) => { - Some(((id, &v2.lamport_version), None)) + Some(((id, &v1.lamport_version), None)) } // Mutated Objects (ObjectIn::Exist(((old_version, _), _)), ObjectOut::ObjectWrite(_), _) => { - Some(((id, &v2.lamport_version), Some(old_version))) + Some(((id, &v1.lamport_version), Some(old_version))) } ( ObjectIn::Exist(((old_version, _), _)), @@ -206,7 +172,7 @@ impl CheckpointTransaction { _ => None, } - })) + }) } } // Lookup Objects in output Objects as well as old versions for mutated objects @@ -231,13 +197,8 @@ impl CheckpointTransaction { pub fn created_objects(&self) -> impl Iterator { // Iterator over (ObjectId, version) for created objects match &self.effects { - TransactionEffects::V1(v1) => Either::Left( - v1.created() - .iter() - .map(|((id, version, _), _)| (id, version)), - ), - TransactionEffects::V2(v2) => { - Either::Right(v2.changed_objects().iter().filter_map(|(id, change)| { + TransactionEffects::V1(v1) => { + v1.changed_objects().iter().filter_map(|(id, change)| { match ( &change.input_state, &change.output_state, @@ -245,7 +206,7 @@ impl CheckpointTransaction { ) { // Created Objects (ObjectIn::NotExist, ObjectOut::ObjectWrite(_), IDOperation::Created) => { - Some((id, &v2.lamport_version)) + Some((id, &v1.lamport_version)) } ( ObjectIn::NotExist, @@ -255,7 +216,7 @@ impl CheckpointTransaction { _ => None, } - })) + }) } } // Lookup Objects in output Objects as well as old versions for mutated objects diff --git a/iota-execution/latest/iota-adapter/src/temporary_store.rs b/iota-execution/latest/iota-adapter/src/temporary_store.rs index dedf18083b0..1f35ae0fcf2 100644 --- a/iota-execution/latest/iota-adapter/src/temporary_store.rs +++ b/iota-execution/latest/iota-adapter/src/temporary_store.rs @@ -257,7 +257,7 @@ impl<'backing> TemporaryStore<'backing> { let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone(); let inner = self.into_inner(); - let effects = TransactionEffects::new_from_execution_v2( + let effects = TransactionEffects::new_from_execution_v1( status, epoch, gas_cost_summary, @@ -444,7 +444,7 @@ impl<'backing> TemporaryStore<'backing> { } pub fn estimate_effects_size_upperbound(&self) -> usize { - TransactionEffects::estimate_effects_size_upperbound_v2( + TransactionEffects::estimate_effects_size_upperbound_v1( self.execution_results.written_objects.len(), self.execution_results.modified_objects.len(), self.input_objects.len(), diff --git a/iota-execution/v0/iota-adapter/src/temporary_store.rs b/iota-execution/v0/iota-adapter/src/temporary_store.rs index dedf18083b0..1f35ae0fcf2 100644 --- a/iota-execution/v0/iota-adapter/src/temporary_store.rs +++ b/iota-execution/v0/iota-adapter/src/temporary_store.rs @@ -257,7 +257,7 @@ impl<'backing> TemporaryStore<'backing> { let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone(); let inner = self.into_inner(); - let effects = TransactionEffects::new_from_execution_v2( + let effects = TransactionEffects::new_from_execution_v1( status, epoch, gas_cost_summary, @@ -444,7 +444,7 @@ impl<'backing> TemporaryStore<'backing> { } pub fn estimate_effects_size_upperbound(&self) -> usize { - TransactionEffects::estimate_effects_size_upperbound_v2( + TransactionEffects::estimate_effects_size_upperbound_v1( self.execution_results.written_objects.len(), self.execution_results.modified_objects.len(), self.input_objects.len(),