diff --git a/crates/sui-core/src/authority.rs b/crates/sui-core/src/authority.rs index d489815ec03f4..a3008fa4960ff 100644 --- a/crates/sui-core/src/authority.rs +++ b/crates/sui-core/src/authority.rs @@ -5113,7 +5113,9 @@ impl NodeStateDump { shared_objects.push(ObjDumpFormat::new(w)) } } - InputSharedObject::ReadDeleted(..) | InputSharedObject::MutateDeleted(..) => (), + InputSharedObject::ReadDeleted(..) + | InputSharedObject::MutateDeleted(..) + | InputSharedObject::Cancelled(..) => (), // TODO: consider record congested objects. } } diff --git a/crates/sui-core/src/checkpoints/causal_order.rs b/crates/sui-core/src/checkpoints/causal_order.rs index c7e3ce6365f4d..6a396cbd1f11f 100644 --- a/crates/sui-core/src/checkpoints/causal_order.rs +++ b/crates/sui-core/src/checkpoints/causal_order.rs @@ -146,6 +146,7 @@ impl RWLockDependencyBuilder { .entry(*effect.transaction_digest()) .or_default() .push(ObjectKey(oid, version)), + InputSharedObject::Cancelled(..) => (), // TODO: confirm that consensus_commit_prologue is always at the beginning of the checkpoint, so that cancelled txn don't need to worry about dependency. } } } diff --git a/crates/sui-core/tests/staged/sui.yaml b/crates/sui-core/tests/staged/sui.yaml index 0e758a2195727..b6b9aaf57b05b 100644 --- a/crates/sui-core/tests/staged/sui.yaml +++ b/crates/sui-core/tests/staged/sui.yaml @@ -222,6 +222,10 @@ CompressedSignature: ZkLogin: NEWTYPE: TYPENAME: ZkLoginAuthenticatorAsBytes +CongestedObjects: + NEWTYPESTRUCT: + SEQ: + TYPENAME: ObjectID ConsensusCommitDigest: NEWTYPESTRUCT: TYPENAME: Digest @@ -447,6 +451,11 @@ ExecutionFailureStatus: SharedObjectOperationNotAllowed: UNIT 32: InputObjectDeleted: UNIT + 33: + ExecutionCancelledDueToSharedObjectCongestion: + STRUCT: + - congested_objects: + TYPENAME: CongestedObjects ExecutionStatus: ENUM: 0: @@ -1073,6 +1082,10 @@ UnchangedSharedKind: ReadDeleted: NEWTYPE: TYPENAME: SequenceNumber + 3: + Cancelled: + NEWTYPE: + TYPENAME: SequenceNumber UpgradeInfo: STRUCT: - upgraded_id: diff --git a/crates/sui-graphql-rpc/schema/current_progress_schema.graphql b/crates/sui-graphql-rpc/schema/current_progress_schema.graphql index 277462a920808..065d2c4a404f2 100644 --- a/crates/sui-graphql-rpc/schema/current_progress_schema.graphql +++ b/crates/sui-graphql-rpc/schema/current_progress_schema.graphql @@ -3117,6 +3117,20 @@ type SharedInput { mutable: Boolean! } +""" +The transaction accpeted a shared object as input, but its execution was cancelled. +""" +type SharedObjectCancelled { + """ + ID of the shared object. + """ + address: SuiAddress! + """ + The assigned shared object version. It is a special version indicating transaction cancellation reason. + """ + version: Int! +} + """ The transaction accepted a shared object as input, but it was deleted before the transaction executed. @@ -3885,7 +3899,7 @@ This information is considered part of the effects, because although the transac the shared object as input, consensus must schedule it and pick the version that is actually used. """ -union UnchangedSharedObject = SharedObjectRead | SharedObjectDelete +union UnchangedSharedObject = SharedObjectRead | SharedObjectDelete | SharedObjectCancelled type UnchangedSharedObjectConnection { """ diff --git a/crates/sui-graphql-rpc/src/types/unchanged_shared_object.rs b/crates/sui-graphql-rpc/src/types/unchanged_shared_object.rs index cd0f77805d03f..a80c216871e95 100644 --- a/crates/sui-graphql-rpc/src/types/unchanged_shared_object.rs +++ b/crates/sui-graphql-rpc/src/types/unchanged_shared_object.rs @@ -14,6 +14,7 @@ use super::{object_read::ObjectRead, sui_address::SuiAddress}; pub(crate) enum UnchangedSharedObject { Read(SharedObjectRead), Delete(SharedObjectDelete), + Cancelled(SharedObjectCancelled), } /// The transaction accepted a shared object as input, but only to read it. @@ -39,6 +40,16 @@ pub(crate) struct SharedObjectDelete { mutable: bool, } +/// The transaction accpeted a shared object as input, but its execution was cancelled. +#[derive(SimpleObject)] +pub(crate) struct SharedObjectCancelled { + /// ID of the shared object. + address: SuiAddress, + + /// The assigned shared object version. It is a special version indicating transaction cancellation reason. + version: u64, +} + /// Error for converting from an `InputSharedObject`. pub(crate) struct SharedObjectChanged; @@ -71,6 +82,11 @@ impl UnchangedSharedObject { version: v.value(), mutable: true, })), + + I::Cancelled(id, v) => Ok(U::Cancelled(SharedObjectCancelled { + address: id.into(), + version: v.value(), + })), } } } diff --git a/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap b/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap index 7e02b01da8968..a4338353c81fc 100644 --- a/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap +++ b/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap @@ -3121,6 +3121,20 @@ type SharedInput { mutable: Boolean! } +""" +The transaction accpeted a shared object as input, but its execution was cancelled. +""" +type SharedObjectCancelled { + """ + ID of the shared object. + """ + address: SuiAddress! + """ + The assigned shared object version. It is a special version indicating transaction cancellation reason. + """ + version: Int! +} + """ The transaction accepted a shared object as input, but it was deleted before the transaction executed. @@ -3889,7 +3903,7 @@ This information is considered part of the effects, because although the transac the shared object as input, consensus must schedule it and pick the version that is actually used. """ -union UnchangedSharedObject = SharedObjectRead | SharedObjectDelete +union UnchangedSharedObject = SharedObjectRead | SharedObjectDelete | SharedObjectCancelled type UnchangedSharedObjectConnection { """ diff --git a/crates/sui-types/src/digests.rs b/crates/sui-types/src/digests.rs index eafe74c7df678..95a243155bb2b 100644 --- a/crates/sui-types/src/digests.rs +++ b/crates/sui-types/src/digests.rs @@ -820,6 +820,7 @@ impl ObjectDigest { pub const MAX: ObjectDigest = Self::new([u8::MAX; 32]); pub const OBJECT_DIGEST_DELETED_BYTE_VAL: u8 = 99; pub const OBJECT_DIGEST_WRAPPED_BYTE_VAL: u8 = 88; + pub const OBJECT_DIGEST_CANCELLED_BYTE_VAL: u8 = 77; /// A marker that signifies the object is deleted. pub const OBJECT_DIGEST_DELETED: ObjectDigest = @@ -829,6 +830,9 @@ impl ObjectDigest { pub const OBJECT_DIGEST_WRAPPED: ObjectDigest = Self::new([Self::OBJECT_DIGEST_WRAPPED_BYTE_VAL; 32]); + pub const OBJECT_DIGEST_CANCELLED: ObjectDigest = + Self::new([Self::OBJECT_DIGEST_CANCELLED_BYTE_VAL; 32]); + pub const fn new(digest: [u8; 32]) -> Self { Self(Digest::new(digest)) } diff --git a/crates/sui-types/src/effects/effects_v1.rs b/crates/sui-types/src/effects/effects_v1.rs index 07b816f6f6478..14c683a7fa97e 100644 --- a/crates/sui-types/src/effects/effects_v1.rs +++ b/crates/sui-types/src/effects/effects_v1.rs @@ -276,6 +276,9 @@ impl TransactionEffectsAPI for TransactionEffectsV1 { self.shared_objects .push((id, version, ObjectDigest::OBJECT_DIGEST_DELETED)); } + InputSharedObject::Cancelled(..) => { + panic!("Transaction cancellation is not supported in effect v1"); + } } } diff --git a/crates/sui-types/src/effects/effects_v2.rs b/crates/sui-types/src/effects/effects_v2.rs index c4a369f0e6885..363799820cc12 100644 --- a/crates/sui-types/src/effects/effects_v2.rs +++ b/crates/sui-types/src/effects/effects_v2.rs @@ -118,6 +118,9 @@ impl TransactionEffectsAPI for TransactionEffectsV2 { UnchangedSharedKind::ReadDeleted(seqno) => { InputSharedObject::ReadDeleted(*id, *seqno) } + UnchangedSharedKind::Cancelled(seqno) => { + InputSharedObject::Cancelled(*id, *seqno) + } }, )) .collect() @@ -354,6 +357,9 @@ impl TransactionEffectsAPI for TransactionEffectsV2 { 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))), } } @@ -420,6 +426,10 @@ impl TransactionEffectsV2 { Some((id, UnchangedSharedKind::ReadDeleted(version))) } } + SharedInput::Cancelled((id, version)) => { + debug_assert!(!changed_objects.contains_key(&id)); + Some((id, UnchangedSharedKind::Cancelled(version))) + } }) .collect(); let changed_objects: Vec<_> = changed_objects.into_iter().collect(); @@ -575,4 +585,6 @@ pub enum UnchangedSharedKind { 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), } diff --git a/crates/sui-types/src/effects/mod.rs b/crates/sui-types/src/effects/mod.rs index d9ba317e25c34..b047c4bed90f0 100644 --- a/crates/sui-types/src/effects/mod.rs +++ b/crates/sui-types/src/effects/mod.rs @@ -304,6 +304,7 @@ pub enum InputSharedObject { ReadOnly(ObjectRef), ReadDeleted(ObjectID, SequenceNumber), MutateDeleted(ObjectID, SequenceNumber), + Cancelled(ObjectID, SequenceNumber), } impl InputSharedObject { @@ -319,6 +320,9 @@ impl InputSharedObject { | InputSharedObject::MutateDeleted(id, version) => { (*id, *version, ObjectDigest::OBJECT_DIGEST_DELETED) } + InputSharedObject::Cancelled(id, version) => { + (*id, *version, ObjectDigest::OBJECT_DIGEST_CANCELLED) + } } } } @@ -372,7 +376,8 @@ pub trait TransactionEffectsAPI { InputSharedObject::MutateDeleted(id, _) => Some(id), InputSharedObject::Mutate(..) | InputSharedObject::ReadOnly(..) - | InputSharedObject::ReadDeleted(..) => None, + | InputSharedObject::ReadDeleted(..) + | InputSharedObject::Cancelled(..) => None, }) .collect() } diff --git a/crates/sui-types/src/execution.rs b/crates/sui-types/src/execution.rs index d35ee107df57b..20b4a9a58edbb 100644 --- a/crates/sui-types/src/execution.rs +++ b/crates/sui-types/src/execution.rs @@ -40,6 +40,7 @@ pub type DeletedSharedObjects = Vec; pub enum SharedInput { Existing(ObjectRef), Deleted(DeletedSharedObjectInfo), + Cancelled((ObjectID, SequenceNumber)), } impl SuiResolver for T diff --git a/crates/sui-types/src/execution_status.rs b/crates/sui-types/src/execution_status.rs index 9915ae9c3e344..be415c23f1cf8 100644 --- a/crates/sui-types/src/execution_status.rs +++ b/crates/sui-types/src/execution_status.rs @@ -5,7 +5,7 @@ use crate::ObjectID; use move_binary_format::file_format::{CodeOffset, TypeParameterIndex}; use move_core_types::language_storage::ModuleId; use serde::{Deserialize, Serialize}; -use std::fmt::{Display, Formatter}; +use std::fmt::{self, Display, Formatter}; use sui_macros::EnumVariantOrder; use thiserror::Error; @@ -25,6 +25,18 @@ pub enum ExecutionStatus { }, } +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] +pub struct CongestedObjects(pub Vec); + +impl fmt::Display for CongestedObjects { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + for obj in &self.0 { + write!(f, "{}, ", obj)?; + } + Ok(()) + } +} + #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Error, EnumVariantOrder)] pub enum ExecutionFailureStatus { // @@ -188,6 +200,9 @@ pub enum ExecutionFailureStatus { #[error("Certificate cannot be executed due to a dependency on a deleted shared object")] InputObjectDeleted, + + #[error("Certificate is cancelled due to congestion on shared objects: {congested_objects}")] + ExecutionCancelledDueToSharedObjectCongestion { congested_objects: CongestedObjects }, // NOTE: if you want to add a new enum, // please add it at the end for Rust SDK backward compatibility. } diff --git a/crates/sui-types/tests/staged/exec_failure_status.yaml b/crates/sui-types/tests/staged/exec_failure_status.yaml index 5638031da7218..f2506d965be19 100644 --- a/crates/sui-types/tests/staged/exec_failure_status.yaml +++ b/crates/sui-types/tests/staged/exec_failure_status.yaml @@ -32,3 +32,4 @@ 30: SuiMoveVerificationTimedout 31: SharedObjectOperationNotAllowed 32: InputObjectDeleted +33: ExecutionCancelledDueToSharedObjectCongestion diff --git a/sui-execution/v0/sui-adapter/src/temporary_store.rs b/sui-execution/v0/sui-adapter/src/temporary_store.rs index 3984b567bfa85..edca391946d52 100644 --- a/sui-execution/v0/sui-adapter/src/temporary_store.rs +++ b/sui-execution/v0/sui-adapter/src/temporary_store.rs @@ -265,6 +265,9 @@ impl<'backing> TemporaryStore<'backing> { SharedInput::Deleted(_) => { unreachable!("Shared object deletion not supported in effects v1") } + SharedInput::Cancelled(_) => { + unreachable!("Per object congestion control not supported in effects v1.") + } }) .collect(); diff --git a/sui-execution/v1/sui-adapter/src/temporary_store.rs b/sui-execution/v1/sui-adapter/src/temporary_store.rs index bb4ec4f1be8be..89ebb5731526b 100644 --- a/sui-execution/v1/sui-adapter/src/temporary_store.rs +++ b/sui-execution/v1/sui-adapter/src/temporary_store.rs @@ -213,6 +213,9 @@ impl<'backing> TemporaryStore<'backing> { SharedInput::Deleted(_) => { unreachable!("Shared object deletion not supported in effects v1") } + SharedInput::Cancelled(_) => { + unreachable!("Per object congestion control not supported in effects v1.") + } }) .collect(); self.into_effects_v1( diff --git a/sui-execution/v2/sui-adapter/src/temporary_store.rs b/sui-execution/v2/sui-adapter/src/temporary_store.rs index 103cbb16b0ef3..dd9a4823262ab 100644 --- a/sui-execution/v2/sui-adapter/src/temporary_store.rs +++ b/sui-execution/v2/sui-adapter/src/temporary_store.rs @@ -231,6 +231,9 @@ impl<'backing> TemporaryStore<'backing> { SharedInput::Deleted(_) => { unreachable!("Shared object deletion not supported in effects v1") } + SharedInput::Cancelled(_) => { + unreachable!("Per object congestion control not supported in effects v1.") + } }) .collect(); self.into_effects_v1(