Skip to main content

sui_core/authority/
shared_object_version_manager.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use mysten_common::ZipDebugEqIteratorExt;
5use mysten_common::debug_fatal;
6
7use crate::authority::AuthorityPerEpochStore;
8use crate::authority::authority_per_epoch_store::CancelConsensusCertificateReason;
9use crate::execution_cache::ObjectCacheRead;
10use either::Either;
11use std::collections::BTreeMap;
12use std::collections::HashMap;
13use std::collections::HashSet;
14use sui_types::SUI_ACCUMULATOR_ROOT_OBJECT_ID;
15use sui_types::SUI_CLOCK_OBJECT_ID;
16use sui_types::SUI_CLOCK_OBJECT_SHARED_VERSION;
17use sui_types::SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID;
18use sui_types::base_types::ConsensusObjectSequenceKey;
19use sui_types::base_types::ConsensusObjectVersion;
20use sui_types::base_types::ObjectID;
21use sui_types::base_types::SystemObjectVersions;
22use sui_types::base_types::TransactionDigest;
23use sui_types::committee::EpochId;
24use sui_types::crypto::RandomnessRound;
25use sui_types::effects::{TransactionEffects, TransactionEffectsAPI};
26use sui_types::executable_transaction::VerifiedExecutableTransaction;
27use sui_types::executable_transaction::VerifiedExecutableTransactionWithAliases;
28use sui_types::storage::{
29    ObjectKey, transaction_non_shared_input_object_keys, transaction_receiving_object_keys,
30};
31use sui_types::transaction::SharedObjectMutability;
32use sui_types::transaction::{SharedInputObject, TransactionDataAPI, TransactionKey};
33use sui_types::{
34    IMPLICITLY_READ_SYSTEM_OBJECTS, SUI_RANDOMNESS_STATE_OBJECT_ID, base_types::SequenceNumber,
35    error::SuiResult,
36};
37use tracing::trace;
38
39pub struct SharedObjVerManager {}
40
41/// Version assignments for a single transaction
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct AssignedVersions {
44    pub shared_object_versions: Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
45    /// Versions of system objects, keyed by object ID, that this transaction may read during
46    /// execution but that are not part of its declared shared inputs. Each version is assigned
47    /// deterministically during consensus sequencing, so that every validator reads the same
48    /// version of the object.
49    ///
50    /// Today this holds the accumulator root version (as of the beginning of the consensus
51    /// commit this transaction belongs to) and the forwarding address registry version (as of the
52    /// point in the commit at which this transaction is sequenced). The accumulator root qualifies
53    /// because it is written at the end of every commit, so there is always a well-defined prior
54    /// version to read from.
55    pub system_object_versions: SystemObjectVersions,
56}
57
58impl AssignedVersions {
59    pub fn new(
60        shared_object_versions: Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
61        system_object_versions: SystemObjectVersions,
62    ) -> Self {
63        debug_assert!(
64            shared_object_versions
65                .iter()
66                .filter(|((id, _), version)| {
67                    id.is_implicitly_read_system_object() && !version.is_cancelled()
68                })
69                .all(|((id, _), version)| {
70                    system_object_versions
71                        .get(id)
72                        .is_none_or(|system_version| system_version.version == *version)
73                }),
74            "declared versions {shared_object_versions:?} disagree with {system_object_versions:?}"
75        );
76        Self {
77            shared_object_versions,
78            system_object_versions,
79        }
80    }
81
82    pub fn empty() -> Self {
83        Self::new(vec![], SystemObjectVersions::empty())
84    }
85
86    /// Sets the accumulator root as a system object read during execution. For test paths
87    /// that execute directly: the test version-assignment helper assigns no root version,
88    /// but execution reads the root implicitly for any object funds withdraw.
89    pub fn with_accumulator_version_for_testing(mut self, version: ConsensusObjectVersion) -> Self {
90        self.system_object_versions = SystemObjectVersions::new(
91            Some(version),
92            self.system_object_versions
93                .get(&sui_types::SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID),
94        );
95        self
96    }
97
98    /// Construct with only the accumulator root as the system object read during execution.
99    /// Production callers build the full `system_object_versions` map directly.
100    #[cfg(test)]
101    pub fn new_for_testing(
102        shared_object_versions: Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
103        accumulator_version: Option<SequenceNumber>,
104    ) -> Self {
105        Self::new(
106            shared_object_versions,
107            SystemObjectVersions::new(
108                accumulator_version.map(|v| ConsensusObjectVersion {
109                    initial_shared_version: sui_types::object::OBJECT_START_VERSION,
110                    version: v,
111                }),
112                None,
113            ),
114        )
115    }
116
117    /// The accumulator root version this transaction reads, if any.
118    pub fn accumulator_version(&self) -> Option<SequenceNumber> {
119        self.system_object_versions
120            .get(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
121            .map(|v| v.version)
122    }
123
124    pub fn iter(&self) -> impl Iterator<Item = &(ConsensusObjectSequenceKey, SequenceNumber)> {
125        self.shared_object_versions.iter()
126    }
127
128    pub fn as_slice(&self) -> &[(ConsensusObjectSequenceKey, SequenceNumber)] {
129        &self.shared_object_versions
130    }
131}
132
133#[derive(Default, Debug, PartialEq, Eq)]
134pub struct AssignedTxAndVersions(pub Vec<(TransactionKey, AssignedVersions)>);
135
136impl AssignedTxAndVersions {
137    pub fn new(assigned_versions: Vec<(TransactionKey, AssignedVersions)>) -> Self {
138        Self(assigned_versions)
139    }
140
141    pub fn into_map(self) -> HashMap<TransactionKey, AssignedVersions> {
142        self.0.into_iter().collect()
143    }
144}
145
146/// A wrapper around things that can be scheduled for execution by the assigning of
147/// shared object versions.
148#[derive(Clone)]
149pub enum Schedulable<T = VerifiedExecutableTransaction> {
150    Transaction(T),
151    RandomnessStateUpdate(EpochId, RandomnessRound),
152    AccumulatorSettlement(EpochId, u64 /* checkpoint height */),
153    ConsensusCommitPrologue(EpochId, u64 /* round */, u32 /* sub_dag_index */),
154}
155
156impl From<VerifiedExecutableTransaction> for Schedulable<VerifiedExecutableTransaction> {
157    fn from(tx: VerifiedExecutableTransaction) -> Self {
158        Schedulable::Transaction(tx)
159    }
160}
161
162impl From<Schedulable<VerifiedExecutableTransactionWithAliases>>
163    for Schedulable<VerifiedExecutableTransaction>
164{
165    fn from(schedulable: Schedulable<VerifiedExecutableTransactionWithAliases>) -> Self {
166        match schedulable {
167            Schedulable::Transaction(tx) => Schedulable::Transaction(tx.into_tx()),
168            Schedulable::RandomnessStateUpdate(epoch, round) => {
169                Schedulable::RandomnessStateUpdate(epoch, round)
170            }
171            Schedulable::AccumulatorSettlement(epoch, checkpoint_height) => {
172                Schedulable::AccumulatorSettlement(epoch, checkpoint_height)
173            }
174            Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index) => {
175                Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index)
176            }
177        }
178    }
179}
180
181// AsTx is like Deref, in that it allows us to use either refs or values in Schedulable.
182// Deref does not work because it conflicts with the impl of Deref for VerifiedExecutableTransaction.
183pub trait AsTx {
184    fn as_tx(&self) -> &VerifiedExecutableTransaction;
185}
186
187impl AsTx for VerifiedExecutableTransaction {
188    fn as_tx(&self) -> &VerifiedExecutableTransaction {
189        self
190    }
191}
192
193impl AsTx for &'_ VerifiedExecutableTransaction {
194    fn as_tx(&self) -> &VerifiedExecutableTransaction {
195        self
196    }
197}
198
199impl AsTx for VerifiedExecutableTransactionWithAliases {
200    fn as_tx(&self) -> &VerifiedExecutableTransaction {
201        self.tx()
202    }
203}
204
205impl AsTx for &'_ VerifiedExecutableTransactionWithAliases {
206    fn as_tx(&self) -> &VerifiedExecutableTransaction {
207        self.tx()
208    }
209}
210
211impl Schedulable<&'_ VerifiedExecutableTransaction> {
212    // Cannot use the blanket ToOwned trait impl because it just calls clone.
213    pub fn to_owned_schedulable(&self) -> Schedulable<VerifiedExecutableTransaction> {
214        match self {
215            Schedulable::Transaction(tx) => Schedulable::Transaction((*tx).clone()),
216            Schedulable::RandomnessStateUpdate(epoch, round) => {
217                Schedulable::RandomnessStateUpdate(*epoch, *round)
218            }
219            Schedulable::AccumulatorSettlement(epoch, checkpoint_height) => {
220                Schedulable::AccumulatorSettlement(*epoch, *checkpoint_height)
221            }
222            Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index) => {
223                Schedulable::ConsensusCommitPrologue(*epoch, *round, *sub_dag_index)
224            }
225        }
226    }
227}
228
229impl<T> Schedulable<T> {
230    pub fn as_tx(&self) -> Option<&VerifiedExecutableTransaction>
231    where
232        T: AsTx,
233    {
234        match self {
235            Schedulable::Transaction(tx) => Some(tx.as_tx()),
236            Schedulable::RandomnessStateUpdate(_, _) => None,
237            Schedulable::AccumulatorSettlement(_, _) => None,
238            Schedulable::ConsensusCommitPrologue(_, _, _) => None,
239        }
240    }
241
242    pub fn shared_input_objects(
243        &self,
244        epoch_store: &AuthorityPerEpochStore,
245    ) -> impl Iterator<Item = SharedInputObject> + '_
246    where
247        T: AsTx,
248    {
249        match self {
250            Schedulable::Transaction(tx) => Either::Left(tx.as_tx().shared_input_objects()),
251            Schedulable::RandomnessStateUpdate(_, _) => {
252                Either::Right(std::iter::once(SharedInputObject {
253                    id: SUI_RANDOMNESS_STATE_OBJECT_ID,
254                    initial_shared_version: epoch_store
255                        .epoch_start_config()
256                        .randomness_obj_initial_shared_version()
257                        .expect("randomness obj initial shared version should be set"),
258                    mutability: SharedObjectMutability::Mutable,
259                }))
260            }
261            Schedulable::AccumulatorSettlement(_, _) => {
262                Either::Right(std::iter::once(SharedInputObject {
263                    id: SUI_ACCUMULATOR_ROOT_OBJECT_ID,
264                    initial_shared_version: epoch_store
265                        .epoch_start_config()
266                        .accumulator_root_obj_initial_shared_version()
267                        .expect("accumulator root obj initial shared version should be set"),
268                    mutability: SharedObjectMutability::Mutable,
269                }))
270            }
271            Schedulable::ConsensusCommitPrologue(_, _, _) => {
272                Either::Right(std::iter::once(SharedInputObject {
273                    id: SUI_CLOCK_OBJECT_ID,
274                    initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
275                    mutability: SharedObjectMutability::Mutable,
276                }))
277            }
278        }
279    }
280
281    pub fn non_shared_input_object_keys(&self) -> Vec<ObjectKey>
282    where
283        T: AsTx,
284    {
285        match self {
286            Schedulable::Transaction(tx) => transaction_non_shared_input_object_keys(tx.as_tx())
287                .expect("Transaction input should have been verified"),
288            Schedulable::RandomnessStateUpdate(_, _) => vec![],
289            Schedulable::AccumulatorSettlement(_, _) => vec![],
290            Schedulable::ConsensusCommitPrologue(_, _, _) => vec![],
291        }
292    }
293
294    pub fn receiving_object_keys(&self) -> Vec<ObjectKey>
295    where
296        T: AsTx,
297    {
298        match self {
299            Schedulable::Transaction(tx) => transaction_receiving_object_keys(tx.as_tx()),
300            Schedulable::RandomnessStateUpdate(_, _) => vec![],
301            Schedulable::AccumulatorSettlement(_, _) => vec![],
302            Schedulable::ConsensusCommitPrologue(_, _, _) => vec![],
303        }
304    }
305
306    pub fn key(&self) -> TransactionKey
307    where
308        T: AsTx,
309    {
310        match self {
311            Schedulable::Transaction(tx) => tx.as_tx().key(),
312            Schedulable::RandomnessStateUpdate(epoch, round) => {
313                TransactionKey::RandomnessRound(*epoch, *round)
314            }
315            Schedulable::AccumulatorSettlement(epoch, checkpoint_height) => {
316                TransactionKey::AccumulatorSettlement(*epoch, *checkpoint_height)
317            }
318            Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index) => {
319                TransactionKey::ConsensusCommitPrologue(*epoch, *round, *sub_dag_index)
320            }
321        }
322    }
323}
324
325#[must_use]
326#[derive(Default, Eq, PartialEq, Debug)]
327pub struct ConsensusSharedObjVerAssignment {
328    pub shared_input_next_versions: HashMap<ConsensusObjectSequenceKey, SequenceNumber>,
329    pub assigned_versions: AssignedTxAndVersions,
330}
331
332impl SharedObjVerManager {
333    pub fn assign_versions_from_consensus<'a, T>(
334        epoch_store: &AuthorityPerEpochStore,
335        cache_reader: &dyn ObjectCacheRead,
336        assignables: impl Iterator<Item = &'a Schedulable<T>> + Clone,
337        cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
338    ) -> SuiResult<ConsensusSharedObjVerAssignment>
339    where
340        T: AsTx + 'a,
341    {
342        let mut shared_input_next_versions = get_or_init_versions(
343            assignables
344                .clone()
345                .flat_map(|a| a.shared_input_objects(epoch_store)),
346            epoch_store,
347            cache_reader,
348        )?;
349        let mut assigned_versions = Vec::new();
350        for assignable in assignables {
351            assert!(
352                !matches!(assignable, Schedulable::AccumulatorSettlement(_, _))
353                    || epoch_store.accumulators_enabled(),
354                "AccumulatorSettlement should not be scheduled when accumulators are disabled"
355            );
356
357            let cert_assigned_versions = Self::assign_versions_for_certificate(
358                epoch_store,
359                assignable,
360                &mut shared_input_next_versions,
361                cancelled_txns,
362            );
363            assigned_versions.push((assignable.key(), cert_assigned_versions));
364        }
365
366        Ok(ConsensusSharedObjVerAssignment {
367            shared_input_next_versions,
368            assigned_versions: AssignedTxAndVersions::new(assigned_versions),
369        })
370    }
371
372    pub fn assign_versions_from_effects(
373        certs_and_effects: &[(
374            &VerifiedExecutableTransaction,
375            &TransactionEffects,
376            // Accumulator version
377            Option<SequenceNumber>,
378        )],
379        epoch_store: &AuthorityPerEpochStore,
380        cache_reader: &dyn ObjectCacheRead,
381    ) -> AssignedTxAndVersions {
382        // We don't care about the results since we can use effects to assign versions.
383        // But we must call it to make sure whenever a consensus object is touched the first time
384        // during an epoch, either through consensus or through checkpoint executor,
385        // its next version must be initialized. This is because we initialize the next version
386        // of a consensus object in an epoch by reading the current version from the object store.
387        // This must be done before we mutate it the first time, otherwise we would be initializing
388        // it with the wrong version.
389        let _ = get_or_init_versions(
390            certs_and_effects.iter().flat_map(|(cert, _, _)| {
391                cert.transaction_data().shared_input_objects().into_iter()
392            }),
393            epoch_store,
394            cache_reader,
395        );
396        let mut assigned_versions = Vec::new();
397        for (cert, effects, accumulator_version) in certs_and_effects {
398            let initial_version_map: BTreeMap<_, _> = cert
399                .transaction_data()
400                .shared_input_objects()
401                .into_iter()
402                .map(|input| input.into_id_and_version())
403                .collect();
404            // Implicitly read system objects retrieve their accessed versions from this map.
405            let accessed_versions: BTreeMap<ObjectID, SequenceNumber> = effects
406                .accessed_consensus_objects()
407                .into_iter()
408                .map(|iso| iso.id_and_version())
409                .collect();
410            let cert_assigned_versions: Vec<_> = accessed_versions
411                .iter()
412                .filter_map(|(id, version)| {
413                    let v = initial_version_map
414                        .get(id)
415                        .map(|initial_version| ((*id, *initial_version), *version));
416                    if v.is_none() {
417                        debug_assert!(
418                            IMPLICITLY_READ_SYSTEM_OBJECTS.contains(id),
419                            "accessed consensus object is neither a declared input nor a known implicitly read system object: \
420                             accessed={accessed_versions:?} declared={initial_version_map:?}"
421                        );
422                    }
423                    v
424                })
425                .collect();
426            if let (Some(effects_version), Some(sequenced_version)) = (
427                accessed_versions.get(&SUI_ACCUMULATOR_ROOT_OBJECT_ID),
428                accumulator_version,
429            ) && !effects_version.is_cancelled()
430                && effects_version != sequenced_version
431            {
432                debug_fatal!(
433                    "accumulator root version from effects {:?} disagrees \
434                        with the reconstructed accumulator version {:?} for tx {:?}",
435                    effects_version,
436                    sequenced_version,
437                    cert.digest()
438                );
439            }
440            let mut system_object_versions: BTreeMap<_, _> =
441                implicitly_read_system_objects(epoch_store)
442                    .into_iter()
443                    .filter_map(|(id, initial_shared_version)| {
444                        let version = *accessed_versions.get(&id)?;
445                        // A cancelled sentinel only appears when the object was also a declared
446                        // input of a cancelled transaction; it is not a version to pin.
447                        (!version.is_cancelled()).then_some((
448                            id,
449                            ConsensusObjectVersion {
450                                initial_shared_version,
451                                version,
452                            },
453                        ))
454                    })
455                    .collect();
456            // Note that for accumulator version, we cannot rely on the one from effects yet, since it won't
457            // be produced until implicitly read system objects are fully shipped. But the old object funds withdraw
458            // still need it. Hence we always use the one provided from the caller (i.e. checkpoint executor).
459            // TODO: Once check_object_funds_withdraw_in_execution is enabled everywhere and the
460            // post-execution ObjectFundsChecker is removed, drop this override (and the
461            // accumulator version threaded from the checkpoint executor) so the accumulator root
462            // is reconstructed from effects exactly like the forwarding address registry.
463            if let Some(version) = *accumulator_version {
464                let initial_shared_version = epoch_store
465                    .epoch_start_config()
466                    .accumulator_root_obj_initial_shared_version()
467                    .expect(
468                        "initial shared version must be known for an implicitly read system object",
469                    );
470                system_object_versions.insert(
471                    SUI_ACCUMULATOR_ROOT_OBJECT_ID,
472                    ConsensusObjectVersion {
473                        initial_shared_version,
474                        version,
475                    },
476                );
477            }
478            let system_object_versions = SystemObjectVersions::from_map(system_object_versions);
479            let tx_key = cert.key();
480            trace!(
481                ?tx_key,
482                ?cert_assigned_versions,
483                ?system_object_versions,
484                "assigned consensus object versions from effects"
485            );
486            assigned_versions.push((
487                tx_key,
488                AssignedVersions::new(cert_assigned_versions, system_object_versions),
489            ));
490        }
491        AssignedTxAndVersions::new(assigned_versions)
492    }
493
494    pub fn assign_versions_for_certificate(
495        epoch_store: &AuthorityPerEpochStore,
496        assignable: &Schedulable<impl AsTx>,
497        shared_input_next_versions: &mut HashMap<ConsensusObjectSequenceKey, SequenceNumber>,
498        cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
499    ) -> AssignedVersions {
500        let shared_input_objects: Vec<_> = assignable.shared_input_objects(epoch_store).collect();
501
502        let system_object_versions = SystemObjectVersions::from_map(
503            implicitly_read_system_objects(epoch_store)
504                .into_iter()
505                .map(|(id, initial_shared_version)| {
506                    let version = *shared_input_next_versions
507                        .get(&(id, initial_shared_version))
508                        .expect(
509                            "implicitly read system objects must be in shared_input_next_versions",
510                        );
511                    (
512                        id,
513                        ConsensusObjectVersion {
514                            initial_shared_version,
515                            version,
516                        },
517                    )
518                })
519                .collect(),
520        );
521
522        if shared_input_objects.is_empty() {
523            // No shared object used by this transaction. No need to assign versions.
524            return AssignedVersions::new(vec![], system_object_versions);
525        }
526
527        let tx_key = assignable.key();
528
529        // Check if the transaction is cancelled due to congestion.
530        let cancellation_info = tx_key
531            .as_digest()
532            .and_then(|tx_digest| cancelled_txns.get(tx_digest));
533        let congested_objects_info: Option<HashSet<_>> =
534            if let Some(CancelConsensusCertificateReason::CongestionOnObjects(congested_objects)) =
535                &cancellation_info
536            {
537                Some(congested_objects.iter().cloned().collect())
538            } else {
539                None
540            };
541        let txn_cancelled = cancellation_info.is_some();
542
543        let mut input_object_keys = assignable.non_shared_input_object_keys();
544        let mut assigned_versions = Vec::with_capacity(shared_input_objects.len());
545        let mut is_exclusively_accessed_input = Vec::with_capacity(shared_input_objects.len());
546        // Record receiving object versions towards the shared version computation.
547        let receiving_object_keys = assignable.receiving_object_keys();
548        input_object_keys.extend(receiving_object_keys);
549
550        if txn_cancelled {
551            // For cancelled transaction due to congestion, assign special versions to all shared objects.
552            // Note that new lamport version does not depend on any shared objects.
553            for SharedInputObject {
554                id,
555                initial_shared_version,
556                ..
557            } in shared_input_objects.iter()
558            {
559                let assigned_version = match cancellation_info {
560                    Some(CancelConsensusCertificateReason::CongestionOnObjects(_)) => {
561                        if congested_objects_info
562                            .as_ref()
563                            .is_some_and(|info| info.contains(id))
564                        {
565                            SequenceNumber::CONGESTED
566                        } else {
567                            SequenceNumber::CANCELLED_READ
568                        }
569                    }
570                    Some(CancelConsensusCertificateReason::DkgFailed) => {
571                        if id == &SUI_RANDOMNESS_STATE_OBJECT_ID {
572                            SequenceNumber::RANDOMNESS_UNAVAILABLE
573                        } else {
574                            SequenceNumber::CANCELLED_READ
575                        }
576                    }
577                    None => unreachable!("cancelled transaction should have cancellation info"),
578                };
579                assigned_versions.push(((*id, *initial_shared_version), assigned_version));
580                is_exclusively_accessed_input.push(false);
581            }
582        } else {
583            for (
584                SharedInputObject {
585                    id,
586                    initial_shared_version,
587                    mutability,
588                },
589                assigned_version,
590            ) in shared_input_objects.iter().map(|obj| {
591                (
592                    obj,
593                    *shared_input_next_versions
594                        .get(&obj.id_and_version())
595                        .unwrap(),
596                )
597            }) {
598                assigned_versions.push(((*id, *initial_shared_version), assigned_version));
599                input_object_keys.push(ObjectKey(*id, assigned_version));
600                is_exclusively_accessed_input.push(mutability.is_exclusive());
601            }
602        }
603
604        let next_version =
605            SequenceNumber::lamport_increment(input_object_keys.iter().map(|obj| obj.1));
606        assert!(
607            next_version.is_valid(),
608            "Assigned version must be valid. Got {:?}",
609            next_version
610        );
611
612        if !txn_cancelled {
613            // Update the next version for the shared objects.
614            assigned_versions
615                .iter()
616                .zip_debug_eq(is_exclusively_accessed_input)
617                .filter_map(|((id, _), mutable)| {
618                    if mutable {
619                        Some((*id, next_version))
620                    } else {
621                        None
622                    }
623                })
624                .for_each(|(id, version)| {
625                    assert!(
626                        version.is_valid(),
627                        "Assigned version must be a valid version."
628                    );
629                    shared_input_next_versions
630                        .insert(id, version)
631                        .expect("Object must exist in shared_input_next_versions.");
632                });
633        }
634
635        trace!(
636            ?tx_key,
637            ?assigned_versions,
638            ?next_version,
639            ?txn_cancelled,
640            "locking shared objects"
641        );
642
643        AssignedVersions::new(assigned_versions, system_object_versions)
644    }
645}
646
647fn implicitly_read_system_objects(
648    epoch_store: &AuthorityPerEpochStore,
649) -> Vec<ConsensusObjectSequenceKey> {
650    let epoch_start_config = epoch_store.epoch_start_config();
651    let mut objects = Vec::new();
652    if epoch_store.accumulators_enabled() {
653        objects.push((
654            SUI_ACCUMULATOR_ROOT_OBJECT_ID,
655            epoch_start_config
656                .accumulator_root_obj_initial_shared_version()
657                .expect("accumulator root obj initial shared version should be set"),
658        ));
659    }
660    if epoch_store.protocol_config().enable_forwarding_addresses()
661        && let Some(initial_shared_version) =
662            epoch_start_config.forwarding_address_registry_obj_initial_shared_version()
663    {
664        objects.push((
665            SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
666            initial_shared_version,
667        ));
668    }
669    objects
670}
671
672fn get_or_init_versions<'a>(
673    shared_input_objects: impl Iterator<Item = SharedInputObject> + 'a,
674    epoch_store: &AuthorityPerEpochStore,
675    cache_reader: &dyn ObjectCacheRead,
676) -> SuiResult<HashMap<ConsensusObjectSequenceKey, SequenceNumber>> {
677    let mut shared_input_objects: Vec<_> = shared_input_objects
678        .map(|so| so.into_id_and_version())
679        .collect();
680
681    shared_input_objects.extend(implicitly_read_system_objects(epoch_store));
682
683    shared_input_objects.sort();
684    shared_input_objects.dedup();
685
686    epoch_store.get_or_init_next_object_versions(&shared_input_objects, cache_reader)
687}
688
689#[cfg(test)]
690mod tests {
691    use super::*;
692
693    use crate::authority::AuthorityState;
694    use crate::authority::shared_object_version_manager::{
695        ConsensusSharedObjVerAssignment, SharedObjVerManager,
696    };
697    use crate::authority::test_authority_builder::TestAuthorityBuilder;
698    use std::collections::{BTreeMap, HashMap};
699    use std::sync::Arc;
700    use sui_protocol_config::ProtocolConfig;
701    use sui_test_transaction_builder::TestTransactionBuilder;
702    use sui_types::base_types::{ObjectID, SequenceNumber, SuiAddress};
703    use sui_types::crypto::{RandomnessRound, get_account_key_pair};
704    use sui_types::digests::ObjectDigest;
705    use sui_types::effects::TestEffectsBuilder;
706    use sui_types::executable_transaction::{
707        CertificateProof, ExecutableTransaction, VerifiedExecutableTransaction,
708    };
709
710    use sui_types::object::Object;
711    use sui_types::transaction::{ObjectArg, SenderSignedData, VerifiedTransaction};
712
713    use sui_types::gas_coin::GAS;
714    use sui_types::transaction::FundsWithdrawalArg;
715    use sui_types::{SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID};
716
717    fn assigned_versions_for_testing(
718        shared_object_versions: Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
719        accumulator_version: Option<SequenceNumber>,
720        forwarding_address_registry_version: Option<SequenceNumber>,
721    ) -> AssignedVersions {
722        let at_start_version = |v| ConsensusObjectVersion {
723            initial_shared_version: sui_types::object::OBJECT_START_VERSION,
724            version: v,
725        };
726        AssignedVersions::new(
727            shared_object_versions,
728            SystemObjectVersions::new(
729                accumulator_version.map(at_start_version),
730                forwarding_address_registry_version.map(at_start_version),
731            ),
732        )
733    }
734
735    #[tokio::test]
736    async fn test_assign_versions_from_consensus_basic() {
737        let shared_object = Object::shared_for_testing();
738        let id = shared_object.id();
739        let init_shared_version = shared_object.owner.start_version().unwrap();
740        let authority = TestAuthorityBuilder::new()
741            .with_starting_objects(std::slice::from_ref(&shared_object))
742            .build()
743            .await;
744        let certs = [
745            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 3),
746            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, false)], 5),
747            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 9),
748            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 11),
749        ];
750        let epoch_store = authority.epoch_store_for_testing();
751        let assignables = certs
752            .iter()
753            .map(Schedulable::Transaction)
754            .collect::<Vec<_>>();
755        let ConsensusSharedObjVerAssignment {
756            shared_input_next_versions,
757            assigned_versions,
758        } = SharedObjVerManager::assign_versions_from_consensus(
759            &epoch_store,
760            authority.get_object_cache_reader().as_ref(),
761            assignables.iter(),
762            &BTreeMap::new(),
763        )
764        .unwrap();
765        // Check that the shared object's next version is always initialized in the epoch store.
766        assert_eq!(
767            epoch_store
768                .get_next_object_version(&id, init_shared_version)
769                .unwrap(),
770            init_shared_version
771        );
772        // Check that the final version of the shared object is the lamport version of the last
773        // transaction.
774        assert_eq!(
775            *shared_input_next_versions
776                .get(&(id, init_shared_version))
777                .unwrap(),
778            SequenceNumber::from_u64(12)
779        );
780        // Check that the version assignment for each transaction is correct.
781        // For a transaction that uses the shared object with mutable=false, it won't update the version
782        // using lamport version, hence the next transaction will use the same version number.
783        // In the following case, certs[2] has the same assignment as certs[1] for this reason.
784        let expected_accumulator_version = SequenceNumber::from_u64(1);
785        let expected_registry_version = SequenceNumber::from_u64(1);
786        assert_eq!(
787            assigned_versions.0,
788            vec![
789                (
790                    certs[0].key(),
791                    assigned_versions_for_testing(
792                        vec![((id, init_shared_version), init_shared_version)],
793                        Some(expected_accumulator_version),
794                        Some(expected_registry_version)
795                    )
796                ),
797                (
798                    certs[1].key(),
799                    assigned_versions_for_testing(
800                        vec![((id, init_shared_version), SequenceNumber::from_u64(4))],
801                        Some(expected_accumulator_version),
802                        Some(expected_registry_version)
803                    )
804                ),
805                (
806                    certs[2].key(),
807                    assigned_versions_for_testing(
808                        vec![((id, init_shared_version), SequenceNumber::from_u64(4))],
809                        Some(expected_accumulator_version),
810                        Some(expected_registry_version)
811                    )
812                ),
813                (
814                    certs[3].key(),
815                    assigned_versions_for_testing(
816                        vec![((id, init_shared_version), SequenceNumber::from_u64(10))],
817                        Some(expected_accumulator_version),
818                        Some(expected_registry_version)
819                    )
820                ),
821            ]
822        );
823    }
824
825    #[tokio::test]
826    async fn test_forwarding_registry_version_follows_consensus_order() {
827        let authority = TestAuthorityBuilder::new().build().await;
828        let epoch_store = authority.epoch_store_for_testing();
829        let registry_initial_version = epoch_store
830            .epoch_start_config()
831            .forwarding_address_registry_obj_initial_shared_version()
832            .unwrap();
833        let registry_key = (
834            SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
835            registry_initial_version,
836        );
837        let certs = [
838            generate_shared_objs_tx_with_gas_version(&[], 3),
839            generate_shared_objs_tx_with_gas_version(
840                &[(
841                    SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
842                    registry_initial_version,
843                    true,
844                )],
845                5,
846            ),
847            generate_shared_objs_tx_with_gas_version(&[], 1),
848        ];
849        let assignables = certs
850            .iter()
851            .map(Schedulable::Transaction)
852            .collect::<Vec<_>>();
853        let ConsensusSharedObjVerAssignment {
854            shared_input_next_versions,
855            assigned_versions,
856        } = SharedObjVerManager::assign_versions_from_consensus(
857            &epoch_store,
858            authority.get_object_cache_reader().as_ref(),
859            assignables.iter(),
860            &BTreeMap::new(),
861        )
862        .unwrap();
863        let registry_version = |version| ConsensusObjectVersion {
864            initial_shared_version: registry_initial_version,
865            version,
866        };
867        // The registration is sequenced second, so only the last transaction reads the
868        // registry at its post-registration version.
869        assert_eq!(
870            assigned_versions
871                .0
872                .iter()
873                .map(|(_, versions)| {
874                    (
875                        versions.shared_object_versions.clone(),
876                        versions
877                            .system_object_versions
878                            .get(&SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID)
879                            .unwrap(),
880                    )
881                })
882                .collect::<Vec<_>>(),
883            vec![
884                (vec![], registry_version(registry_initial_version)),
885                (
886                    vec![(registry_key, registry_initial_version)],
887                    registry_version(registry_initial_version),
888                ),
889                (vec![], registry_version(SequenceNumber::from_u64(6))),
890            ]
891        );
892        assert_eq!(
893            shared_input_next_versions.get(&registry_key),
894            Some(&SequenceNumber::from_u64(6))
895        );
896    }
897
898    #[tokio::test]
899    async fn test_forwarding_registry_declared_by_cancelled_transaction() {
900        let authority = TestAuthorityBuilder::new().build().await;
901        let epoch_store = authority.epoch_store_for_testing();
902        let registry_initial_version = epoch_store
903            .epoch_start_config()
904            .forwarding_address_registry_obj_initial_shared_version()
905            .unwrap();
906        let registry_key = (
907            SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
908            registry_initial_version,
909        );
910        let certs = [
911            generate_shared_objs_tx_with_gas_version(
912                &[(
913                    SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
914                    registry_initial_version,
915                    true,
916                )],
917                5,
918            ),
919            generate_shared_objs_tx_with_gas_version(&[], 3),
920        ];
921        let cancelled_txns = BTreeMap::from([(
922            *certs[0].digest(),
923            CancelConsensusCertificateReason::CongestionOnObjects(vec![
924                SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
925            ]),
926        )]);
927        let assignables = certs
928            .iter()
929            .map(Schedulable::Transaction)
930            .collect::<Vec<_>>();
931        let ConsensusSharedObjVerAssignment {
932            shared_input_next_versions,
933            assigned_versions,
934        } = SharedObjVerManager::assign_versions_from_consensus(
935            &epoch_store,
936            authority.get_object_cache_reader().as_ref(),
937            assignables.iter(),
938            &cancelled_txns,
939        )
940        .unwrap();
941        // The cancelled declaration carries the sentinel, while the implicit pin keeps the real
942        // version and the registry does not advance for the transaction after it.
943        let expected_version = SequenceNumber::from_u64(1);
944        assert_eq!(
945            assigned_versions.0,
946            vec![
947                (
948                    certs[0].key(),
949                    assigned_versions_for_testing(
950                        vec![(registry_key, SequenceNumber::CONGESTED)],
951                        Some(expected_version),
952                        Some(expected_version)
953                    )
954                ),
955                (
956                    certs[1].key(),
957                    assigned_versions_for_testing(
958                        vec![],
959                        Some(expected_version),
960                        Some(expected_version)
961                    )
962                ),
963            ]
964        );
965        assert_eq!(
966            shared_input_next_versions.get(&registry_key),
967            Some(&registry_initial_version)
968        );
969    }
970
971    #[tokio::test]
972    async fn test_forwarding_registry_version_from_effects() {
973        let authority = TestAuthorityBuilder::new().build().await;
974        let epoch_store = authority.epoch_store_for_testing();
975        let registry_initial_version = epoch_store
976            .epoch_start_config()
977            .forwarding_address_registry_obj_initial_shared_version()
978            .unwrap();
979        let registry_version = SequenceNumber::from_u64(4);
980        let certs = [
981            generate_shared_objs_tx_with_gas_version(&[], 3),
982            generate_shared_objs_tx_with_gas_version(
983                &[(
984                    SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
985                    registry_initial_version,
986                    false,
987                )],
988                5,
989            ),
990        ];
991        let effects = certs
992            .iter()
993            .map(|cert| {
994                TestEffectsBuilder::new(cert.data())
995                    .with_shared_input_versions(BTreeMap::from([(
996                        SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
997                        registry_version,
998                    )]))
999                    .build()
1000            })
1001            .collect::<Vec<_>>();
1002        let assigned_versions = SharedObjVerManager::assign_versions_from_effects(
1003            certs
1004                .iter()
1005                .zip_debug_eq(effects.iter())
1006                .map(|(cert, effect)| (cert, effect, None))
1007                .collect::<Vec<_>>()
1008                .as_slice(),
1009            &epoch_store,
1010            authority.get_object_cache_reader().as_ref(),
1011        );
1012        let system_object_versions = SystemObjectVersions::new(
1013            None,
1014            Some(ConsensusObjectVersion {
1015                initial_shared_version: registry_initial_version,
1016                version: registry_version,
1017            }),
1018        );
1019        assert_eq!(
1020            assigned_versions.0,
1021            vec![
1022                (
1023                    certs[0].key(),
1024                    AssignedVersions::new(vec![], system_object_versions)
1025                ),
1026                (
1027                    certs[1].key(),
1028                    AssignedVersions::new(
1029                        vec![(
1030                            (
1031                                SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
1032                                registry_initial_version
1033                            ),
1034                            registry_version
1035                        )],
1036                        system_object_versions
1037                    )
1038                ),
1039            ]
1040        );
1041    }
1042
1043    #[tokio::test]
1044    async fn test_assign_versions_from_consensus_with_randomness() {
1045        let authority = TestAuthorityBuilder::new().build().await;
1046        let epoch_store = authority.epoch_store_for_testing();
1047        let randomness_obj_version = epoch_store
1048            .epoch_start_config()
1049            .randomness_obj_initial_shared_version()
1050            .unwrap();
1051        let certs = [
1052            VerifiedExecutableTransaction::new_system(
1053                VerifiedTransaction::new_randomness_state_update(
1054                    epoch_store.epoch(),
1055                    RandomnessRound::new(1),
1056                    vec![],
1057                    randomness_obj_version,
1058                ),
1059                epoch_store.epoch(),
1060            ),
1061            generate_shared_objs_tx_with_gas_version(
1062                &[(
1063                    SUI_RANDOMNESS_STATE_OBJECT_ID,
1064                    randomness_obj_version,
1065                    // This can only be false since it's not allowed to use randomness object with mutable=true.
1066                    false,
1067                )],
1068                3,
1069            ),
1070            generate_shared_objs_tx_with_gas_version(
1071                &[(
1072                    SUI_RANDOMNESS_STATE_OBJECT_ID,
1073                    randomness_obj_version,
1074                    false,
1075                )],
1076                5,
1077            ),
1078        ];
1079        let assignables = certs
1080            .iter()
1081            .map(Schedulable::Transaction)
1082            .collect::<Vec<_>>();
1083        let ConsensusSharedObjVerAssignment {
1084            shared_input_next_versions,
1085            assigned_versions,
1086        } = SharedObjVerManager::assign_versions_from_consensus(
1087            &epoch_store,
1088            authority.get_object_cache_reader().as_ref(),
1089            assignables.iter(),
1090            &BTreeMap::new(),
1091        )
1092        .unwrap();
1093        // Check that the randomness object's next version is initialized.
1094        assert_eq!(
1095            epoch_store
1096                .get_next_object_version(&SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version)
1097                .unwrap(),
1098            randomness_obj_version
1099        );
1100        let next_randomness_obj_version = randomness_obj_version.next();
1101        assert_eq!(
1102            *shared_input_next_versions
1103                .get(&(SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version))
1104                .unwrap(),
1105            // Randomness object's version is only incremented by 1 regardless of lamport version.
1106            next_randomness_obj_version
1107        );
1108        let expected_accumulator_version = SequenceNumber::from_u64(1);
1109        let expected_registry_version = SequenceNumber::from_u64(1);
1110        assert_eq!(
1111            assigned_versions.0,
1112            vec![
1113                (
1114                    certs[0].key(),
1115                    assigned_versions_for_testing(
1116                        vec![(
1117                            (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
1118                            randomness_obj_version
1119                        )],
1120                        Some(expected_accumulator_version),
1121                        Some(expected_registry_version)
1122                    )
1123                ),
1124                (
1125                    certs[1].key(),
1126                    // It is critical that the randomness object version is updated before the assignment.
1127                    assigned_versions_for_testing(
1128                        vec![(
1129                            (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
1130                            next_randomness_obj_version
1131                        )],
1132                        Some(expected_accumulator_version),
1133                        Some(expected_registry_version)
1134                    )
1135                ),
1136                (
1137                    certs[2].key(),
1138                    // It is critical that the randomness object version is updated before the assignment.
1139                    assigned_versions_for_testing(
1140                        vec![(
1141                            (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
1142                            next_randomness_obj_version
1143                        )],
1144                        Some(expected_accumulator_version),
1145                        Some(expected_registry_version)
1146                    )
1147                ),
1148            ]
1149        );
1150    }
1151
1152    // Tests shared object version assignment for cancelled transaction.
1153    #[tokio::test]
1154    async fn test_assign_versions_from_consensus_with_cancellation() {
1155        let shared_object_1 = Object::shared_for_testing();
1156        let shared_object_2 = Object::shared_for_testing();
1157        let id1 = shared_object_1.id();
1158        let id2 = shared_object_2.id();
1159        let init_shared_version_1 = shared_object_1.owner.start_version().unwrap();
1160        let init_shared_version_2 = shared_object_2.owner.start_version().unwrap();
1161        let authority = TestAuthorityBuilder::new()
1162            .with_starting_objects(&[shared_object_1.clone(), shared_object_2.clone()])
1163            .build()
1164            .await;
1165        let randomness_obj_version = authority
1166            .epoch_store_for_testing()
1167            .epoch_start_config()
1168            .randomness_obj_initial_shared_version()
1169            .unwrap();
1170
1171        // Generate 5 transactions for testing.
1172        //   tx1: shared_object_1, shared_object_2, owned_object_version = 3
1173        //   tx2: shared_object_1, shared_object_2, owned_object_version = 5
1174        //   tx3: shared_object_1, owned_object_version = 1
1175        //   tx4: shared_object_1, shared_object_2, owned_object_version = 9
1176        //   tx5: shared_object_1, shared_object_2, owned_object_version = 11
1177        //
1178        // Later, we cancel transaction 2 and 4 due to congestion, and 5 due to DKG failure.
1179        // Expected outcome:
1180        //   tx1: both shared objects assign version 1, lamport version = 4
1181        //   tx2: shared objects assign cancelled version, lamport version = 6 due to gas object version = 5
1182        //   tx3: shared object 1 assign version 4, lamport version = 5
1183        //   tx4: shared objects assign cancelled version, lamport version = 10 due to gas object version = 9
1184        //   tx5: shared objects assign cancelled version, lamport version = 12 due to gas object version = 11
1185        let certs = [
1186            generate_shared_objs_tx_with_gas_version(
1187                &[
1188                    (id1, init_shared_version_1, true),
1189                    (id2, init_shared_version_2, true),
1190                ],
1191                3,
1192            ),
1193            generate_shared_objs_tx_with_gas_version(
1194                &[
1195                    (id1, init_shared_version_1, true),
1196                    (id2, init_shared_version_2, true),
1197                ],
1198                5,
1199            ),
1200            generate_shared_objs_tx_with_gas_version(&[(id1, init_shared_version_1, true)], 1),
1201            generate_shared_objs_tx_with_gas_version(
1202                &[
1203                    (id1, init_shared_version_1, true),
1204                    (id2, init_shared_version_2, true),
1205                ],
1206                9,
1207            ),
1208            generate_shared_objs_tx_with_gas_version(
1209                &[
1210                    (
1211                        SUI_RANDOMNESS_STATE_OBJECT_ID,
1212                        randomness_obj_version,
1213                        false,
1214                    ),
1215                    (id2, init_shared_version_2, true),
1216                ],
1217                11,
1218            ),
1219        ];
1220        let epoch_store = authority.epoch_store_for_testing();
1221
1222        // Cancel transactions 2 and 4 due to congestion.
1223        let cancelled_txns: BTreeMap<TransactionDigest, CancelConsensusCertificateReason> = [
1224            (
1225                *certs[1].digest(),
1226                CancelConsensusCertificateReason::CongestionOnObjects(vec![id1]),
1227            ),
1228            (
1229                *certs[3].digest(),
1230                CancelConsensusCertificateReason::CongestionOnObjects(vec![id2]),
1231            ),
1232            (
1233                *certs[4].digest(),
1234                CancelConsensusCertificateReason::DkgFailed,
1235            ),
1236        ]
1237        .into_iter()
1238        .collect();
1239
1240        let assignables = certs
1241            .iter()
1242            .map(Schedulable::Transaction)
1243            .collect::<Vec<_>>();
1244
1245        // Run version assignment logic.
1246        let ConsensusSharedObjVerAssignment {
1247            mut shared_input_next_versions,
1248            assigned_versions,
1249        } = SharedObjVerManager::assign_versions_from_consensus(
1250            &epoch_store,
1251            authority.get_object_cache_reader().as_ref(),
1252            assignables.iter(),
1253            &cancelled_txns,
1254        )
1255        .unwrap();
1256
1257        // Check that the final version of the shared object is the lamport version of the last
1258        // transaction.
1259        shared_input_next_versions
1260            .remove(&(SUI_ACCUMULATOR_ROOT_OBJECT_ID, SequenceNumber::from_u64(1)));
1261        shared_input_next_versions.remove(&(
1262            SUI_FORWARDING_ADDRESS_REGISTRY_OBJECT_ID,
1263            SequenceNumber::from_u64(1),
1264        ));
1265        assert_eq!(
1266            shared_input_next_versions,
1267            HashMap::from([
1268                ((id1, init_shared_version_1), SequenceNumber::from_u64(5)), // determined by tx3
1269                ((id2, init_shared_version_2), SequenceNumber::from_u64(4)), // determined by tx1
1270                (
1271                    (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
1272                    SequenceNumber::from_u64(1)
1273                ), // not mutable
1274            ])
1275        );
1276
1277        // Check that the version assignment for each transaction is correct.
1278        let expected_accumulator_version = SequenceNumber::from_u64(1);
1279        let expected_registry_version = SequenceNumber::from_u64(1);
1280        assert_eq!(
1281            assigned_versions.0,
1282            vec![
1283                (
1284                    certs[0].key(),
1285                    assigned_versions_for_testing(
1286                        vec![
1287                            ((id1, init_shared_version_1), init_shared_version_1),
1288                            ((id2, init_shared_version_2), init_shared_version_2)
1289                        ],
1290                        Some(expected_accumulator_version),
1291                        Some(expected_registry_version)
1292                    )
1293                ),
1294                (
1295                    certs[1].key(),
1296                    assigned_versions_for_testing(
1297                        vec![
1298                            ((id1, init_shared_version_1), SequenceNumber::CONGESTED),
1299                            ((id2, init_shared_version_2), SequenceNumber::CANCELLED_READ),
1300                        ],
1301                        Some(expected_accumulator_version),
1302                        Some(expected_registry_version)
1303                    )
1304                ),
1305                (
1306                    certs[2].key(),
1307                    assigned_versions_for_testing(
1308                        vec![((id1, init_shared_version_1), SequenceNumber::from_u64(4))],
1309                        Some(expected_accumulator_version),
1310                        Some(expected_registry_version)
1311                    )
1312                ),
1313                (
1314                    certs[3].key(),
1315                    assigned_versions_for_testing(
1316                        vec![
1317                            ((id1, init_shared_version_1), SequenceNumber::CANCELLED_READ),
1318                            ((id2, init_shared_version_2), SequenceNumber::CONGESTED)
1319                        ],
1320                        Some(expected_accumulator_version),
1321                        Some(expected_registry_version)
1322                    )
1323                ),
1324                (
1325                    certs[4].key(),
1326                    assigned_versions_for_testing(
1327                        vec![
1328                            (
1329                                (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
1330                                SequenceNumber::RANDOMNESS_UNAVAILABLE
1331                            ),
1332                            ((id2, init_shared_version_2), SequenceNumber::CANCELLED_READ)
1333                        ],
1334                        Some(expected_accumulator_version),
1335                        Some(expected_registry_version)
1336                    )
1337                ),
1338            ]
1339        );
1340    }
1341
1342    #[tokio::test]
1343    async fn test_assign_versions_from_effects() {
1344        let shared_object = Object::shared_for_testing();
1345        let id = shared_object.id();
1346        let init_shared_version = shared_object.owner.start_version().unwrap();
1347        let authority = TestAuthorityBuilder::new()
1348            .with_starting_objects(std::slice::from_ref(&shared_object))
1349            .build()
1350            .await;
1351        let certs = [
1352            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 3),
1353            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, false)], 5),
1354            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 9),
1355            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 11),
1356        ];
1357        let effects = [
1358            TestEffectsBuilder::new(certs[0].data()).build(),
1359            TestEffectsBuilder::new(certs[1].data())
1360                .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(4))]))
1361                .build(),
1362            TestEffectsBuilder::new(certs[2].data())
1363                .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(4))]))
1364                .build(),
1365            TestEffectsBuilder::new(certs[3].data())
1366                .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(10))]))
1367                .build(),
1368        ];
1369        let epoch_store = authority.epoch_store_for_testing();
1370        let assigned_versions = SharedObjVerManager::assign_versions_from_effects(
1371            certs
1372                .iter()
1373                .zip_debug_eq(effects.iter())
1374                .map(|(cert, effect)| (cert, effect, None))
1375                .collect::<Vec<_>>()
1376                .as_slice(),
1377            &epoch_store,
1378            authority.get_object_cache_reader().as_ref(),
1379        );
1380        // Check that the shared object's next version is always initialized in the epoch store.
1381        assert_eq!(
1382            epoch_store
1383                .get_next_object_version(&id, init_shared_version)
1384                .unwrap(),
1385            init_shared_version
1386        );
1387        assert_eq!(
1388            assigned_versions.0,
1389            vec![
1390                (
1391                    certs[0].key(),
1392                    assigned_versions_for_testing(
1393                        vec![((id, init_shared_version), init_shared_version)],
1394                        None,
1395                        None
1396                    )
1397                ),
1398                (
1399                    certs[1].key(),
1400                    assigned_versions_for_testing(
1401                        vec![((id, init_shared_version), SequenceNumber::from_u64(4))],
1402                        None,
1403                        None
1404                    )
1405                ),
1406                (
1407                    certs[2].key(),
1408                    assigned_versions_for_testing(
1409                        vec![((id, init_shared_version), SequenceNumber::from_u64(4))],
1410                        None,
1411                        None
1412                    )
1413                ),
1414                (
1415                    certs[3].key(),
1416                    assigned_versions_for_testing(
1417                        vec![((id, init_shared_version), SequenceNumber::from_u64(10))],
1418                        None,
1419                        None
1420                    )
1421                ),
1422            ]
1423        );
1424    }
1425
1426    /// Generate a transaction that uses shared objects as specified in the parameters.
1427    /// Also uses a gas object with specified version.
1428    /// The version of the gas object is used to manipulate the lamport version of this transaction.
1429    fn generate_shared_objs_tx_with_gas_version(
1430        shared_objects: &[(ObjectID, SequenceNumber, bool)],
1431        gas_object_version: u64,
1432    ) -> VerifiedExecutableTransaction {
1433        let mut tx_builder = TestTransactionBuilder::new(
1434            SuiAddress::ZERO,
1435            (
1436                ObjectID::random(),
1437                SequenceNumber::from_u64(gas_object_version),
1438                ObjectDigest::random(),
1439            ),
1440            0,
1441        );
1442        let tx_data = {
1443            let builder = tx_builder.ptb_builder_mut();
1444            for (shared_object_id, shared_object_init_version, shared_object_mutable) in
1445                shared_objects
1446            {
1447                builder
1448                    .obj(ObjectArg::SharedObject {
1449                        id: *shared_object_id,
1450                        initial_shared_version: *shared_object_init_version,
1451                        mutability: if *shared_object_mutable {
1452                            SharedObjectMutability::Mutable
1453                        } else {
1454                            SharedObjectMutability::Immutable
1455                        },
1456                    })
1457                    .unwrap();
1458            }
1459            tx_builder.build()
1460        };
1461        let tx = SenderSignedData::new(tx_data, vec![]);
1462        VerifiedExecutableTransaction::new_unchecked(ExecutableTransaction::new_from_data_and_sig(
1463            tx,
1464            CertificateProof::new_system(0),
1465        ))
1466    }
1467
1468    struct WithdrawTestContext {
1469        authority: Arc<AuthorityState>,
1470        assignables: Vec<Schedulable<VerifiedExecutableTransaction>>,
1471        shared_objects: Vec<Object>,
1472    }
1473
1474    impl WithdrawTestContext {
1475        pub async fn new() -> Self {
1476            // Create a shared object for testing
1477            let shared_objects = vec![Object::shared_for_testing()];
1478            let mut config = ProtocolConfig::get_for_max_version_UNSAFE();
1479            config.set_enable_accumulators_for_testing(true);
1480            config.set_enable_forwarding_addresses_for_testing(false);
1481            let authority = TestAuthorityBuilder::new()
1482                .with_starting_objects(&shared_objects)
1483                .with_protocol_config(config)
1484                .build()
1485                .await;
1486            Self {
1487                authority,
1488                assignables: vec![],
1489                shared_objects,
1490            }
1491        }
1492
1493        pub fn add_withdraw_transaction(&mut self) -> TransactionKey {
1494            // Generate random sender and gas object for each transaction
1495            let (sender, keypair) = get_account_key_pair();
1496            let gas_object = Object::with_owner_for_testing(sender);
1497            let gas_object_ref = gas_object.compute_object_reference();
1498            // Generate a unique gas price to make the transaction unique.
1499            let gas_price = (self.assignables.len() + 1) as u64;
1500            let mut tx_builder = TestTransactionBuilder::new(sender, gas_object_ref, gas_price);
1501            let tx_data = {
1502                let ptb_builder = tx_builder.ptb_builder_mut();
1503                ptb_builder
1504                    .funds_withdrawal(FundsWithdrawalArg::balance_from_sender(
1505                        200,
1506                        GAS::type_tag(),
1507                    ))
1508                    .unwrap();
1509                tx_builder.build()
1510            };
1511            let cert = VerifiedExecutableTransaction::new_for_testing(tx_data, &keypair);
1512            let key = cert.key();
1513            self.assignables.push(Schedulable::Transaction(cert));
1514            key
1515        }
1516
1517        pub fn add_settlement_transaction(&mut self) -> TransactionKey {
1518            let height = (self.assignables.len() + 1) as u64;
1519            let settlement = Schedulable::AccumulatorSettlement(0, height);
1520            let key = settlement.key();
1521            self.assignables.push(settlement);
1522            key
1523        }
1524
1525        pub fn add_withdraw_with_shared_object_transaction(&mut self) -> TransactionKey {
1526            // Generate random sender and gas object for each transaction
1527            let (sender, keypair) = get_account_key_pair();
1528            let gas_object = Object::with_owner_for_testing(sender);
1529            let gas_object_ref = gas_object.compute_object_reference();
1530            // Generate a unique gas price to make the transaction unique.
1531            let gas_price = (self.assignables.len() + 1) as u64;
1532            let mut tx_builder = TestTransactionBuilder::new(sender, gas_object_ref, gas_price);
1533            let tx_data = {
1534                let ptb_builder = tx_builder.ptb_builder_mut();
1535                // Add shared object to the transaction
1536                if let Some(shared_obj) = self.shared_objects.first() {
1537                    let id = shared_obj.id();
1538                    let init_version = shared_obj.owner.start_version().unwrap();
1539                    ptb_builder
1540                        .obj(ObjectArg::SharedObject {
1541                            id,
1542                            initial_shared_version: init_version,
1543                            mutability: SharedObjectMutability::Mutable,
1544                        })
1545                        .unwrap();
1546                }
1547                // Add balance withdraw
1548                ptb_builder
1549                    .funds_withdrawal(FundsWithdrawalArg::balance_from_sender(
1550                        200,
1551                        GAS::type_tag(),
1552                    ))
1553                    .unwrap();
1554                tx_builder.build()
1555            };
1556            let cert = VerifiedExecutableTransaction::new_for_testing(tx_data, &keypair);
1557            let key = cert.key();
1558            self.assignables.push(Schedulable::Transaction(cert));
1559            key
1560        }
1561
1562        pub fn assign_versions_from_consensus(&self) -> ConsensusSharedObjVerAssignment {
1563            let epoch_store = self.authority.epoch_store_for_testing();
1564            SharedObjVerManager::assign_versions_from_consensus(
1565                &epoch_store,
1566                self.authority.get_object_cache_reader().as_ref(),
1567                self.assignables.iter(),
1568                &BTreeMap::new(),
1569            )
1570            .unwrap()
1571        }
1572    }
1573
1574    #[tokio::test]
1575    async fn test_assign_versions_from_consensus_with_withdraws_simple() {
1576        // Note that we don't need a shared object to trigger withdraw version assignment.
1577        // In fact it is important that this works without a shared object.
1578        let mut ctx = WithdrawTestContext::new().await;
1579
1580        let acc_version = ctx
1581            .authority
1582            .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1583            .unwrap()
1584            .version();
1585
1586        let withdraw_key = ctx.add_withdraw_transaction();
1587        let settlement_key = ctx.add_settlement_transaction();
1588
1589        let assigned_versions = ctx.assign_versions_from_consensus();
1590        assert_eq!(
1591            assigned_versions,
1592            ConsensusSharedObjVerAssignment {
1593                assigned_versions: AssignedTxAndVersions::new(vec![
1594                    (
1595                        withdraw_key,
1596                        assigned_versions_for_testing(vec![], Some(acc_version), None)
1597                    ),
1598                    (
1599                        settlement_key,
1600                        assigned_versions_for_testing(
1601                            vec![((SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version), acc_version)],
1602                            Some(acc_version),
1603                            None
1604                        )
1605                    ),
1606                ]),
1607                shared_input_next_versions: HashMap::from([(
1608                    (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1609                    acc_version.next()
1610                )]),
1611            }
1612        );
1613    }
1614
1615    #[tokio::test]
1616    async fn test_assign_versions_from_consensus_with_multiple_withdraws_and_settlements() {
1617        // Test with multiple withdrawals and multiple settlements, with settlement as the last transaction
1618        let mut ctx = WithdrawTestContext::new().await;
1619
1620        let acc_version = ctx
1621            .authority
1622            .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1623            .unwrap()
1624            .version();
1625
1626        // First withdrawal and settlement
1627        let withdraw_key1 = ctx.add_withdraw_transaction();
1628        let settlement_key1 = ctx.add_settlement_transaction();
1629
1630        // Second withdrawal and settlement
1631        let withdraw_key2 = ctx.add_withdraw_transaction();
1632        let settlement_key2 = ctx.add_settlement_transaction();
1633
1634        // Third withdrawal and final settlement
1635        let withdraw_key3 = ctx.add_withdraw_transaction();
1636        let settlement_key3 = ctx.add_settlement_transaction();
1637
1638        let assigned_versions = ctx.assign_versions_from_consensus();
1639        assert_eq!(
1640            assigned_versions,
1641            ConsensusSharedObjVerAssignment {
1642                assigned_versions: AssignedTxAndVersions::new(vec![
1643                    (
1644                        withdraw_key1,
1645                        assigned_versions_for_testing(vec![], Some(acc_version), None)
1646                    ),
1647                    (
1648                        settlement_key1,
1649                        assigned_versions_for_testing(
1650                            vec![((SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version), acc_version)],
1651                            Some(acc_version),
1652                            None
1653                        )
1654                    ),
1655                    (
1656                        withdraw_key2,
1657                        assigned_versions_for_testing(vec![], Some(acc_version.next()), None)
1658                    ),
1659                    (
1660                        settlement_key2,
1661                        assigned_versions_for_testing(
1662                            vec![(
1663                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1664                                acc_version.next()
1665                            )],
1666                            Some(acc_version.next()),
1667                            None
1668                        )
1669                    ),
1670                    (
1671                        withdraw_key3,
1672                        assigned_versions_for_testing(
1673                            vec![],
1674                            Some(acc_version.next().next()),
1675                            None
1676                        )
1677                    ),
1678                    (
1679                        settlement_key3,
1680                        assigned_versions_for_testing(
1681                            vec![(
1682                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1683                                acc_version.next().next()
1684                            )],
1685                            Some(acc_version.next().next()),
1686                            None
1687                        )
1688                    ),
1689                ]),
1690                shared_input_next_versions: HashMap::from([(
1691                    (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1692                    acc_version.next().next().next()
1693                )]),
1694            }
1695        );
1696    }
1697
1698    #[tokio::test]
1699    async fn test_assign_versions_from_consensus_with_withdraw_and_shared_object() {
1700        // Test that a transaction can have both a withdrawal and use a shared object
1701        let mut ctx = WithdrawTestContext::new().await;
1702
1703        // Get the shared object info from the context
1704        let shared_obj_id = ctx.shared_objects[0].id();
1705        let shared_obj_version = ctx.shared_objects[0].owner.start_version().unwrap();
1706
1707        let acc_version = ctx
1708            .authority
1709            .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1710            .unwrap()
1711            .version();
1712
1713        let withdraw_with_shared_key = ctx.add_withdraw_with_shared_object_transaction();
1714        let settlement_key = ctx.add_settlement_transaction();
1715
1716        let assigned_versions = ctx.assign_versions_from_consensus();
1717        assert_eq!(
1718            assigned_versions,
1719            ConsensusSharedObjVerAssignment {
1720                assigned_versions: AssignedTxAndVersions::new(vec![
1721                    (
1722                        withdraw_with_shared_key,
1723                        assigned_versions_for_testing(
1724                            vec![((shared_obj_id, shared_obj_version), shared_obj_version)],
1725                            Some(acc_version),
1726                            None
1727                        )
1728                    ),
1729                    (
1730                        settlement_key,
1731                        assigned_versions_for_testing(
1732                            vec![((SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version), acc_version)],
1733                            Some(acc_version),
1734                            None
1735                        )
1736                    ),
1737                ]),
1738                shared_input_next_versions: HashMap::from([
1739                    (
1740                        (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1741                        acc_version.next()
1742                    ),
1743                    (
1744                        (shared_obj_id, shared_obj_version),
1745                        shared_obj_version.next()
1746                    ),
1747                ]),
1748            }
1749        );
1750    }
1751}