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;
5
6use crate::authority::AuthorityPerEpochStore;
7use crate::authority::authority_per_epoch_store::CancelConsensusCertificateReason;
8use crate::execution_cache::ObjectCacheRead;
9use either::Either;
10use std::collections::BTreeMap;
11use std::collections::HashMap;
12use std::collections::HashSet;
13use sui_types::SUI_ACCUMULATOR_ROOT_OBJECT_ID;
14use sui_types::SUI_CLOCK_OBJECT_ID;
15use sui_types::SUI_CLOCK_OBJECT_SHARED_VERSION;
16use sui_types::base_types::ConsensusObjectSequenceKey;
17use sui_types::base_types::ObjectID;
18use sui_types::base_types::TransactionDigest;
19use sui_types::committee::EpochId;
20use sui_types::crypto::RandomnessRound;
21use sui_types::effects::{TransactionEffects, TransactionEffectsAPI};
22use sui_types::executable_transaction::VerifiedExecutableTransaction;
23use sui_types::executable_transaction::VerifiedExecutableTransactionWithAliases;
24use sui_types::storage::{
25    ObjectKey, transaction_non_shared_input_object_keys, transaction_receiving_object_keys,
26};
27use sui_types::transaction::SharedObjectMutability;
28use sui_types::transaction::{SharedInputObject, TransactionDataAPI, TransactionKey};
29use sui_types::{SUI_RANDOMNESS_STATE_OBJECT_ID, base_types::SequenceNumber, error::SuiResult};
30use tracing::trace;
31
32pub struct SharedObjVerManager {}
33
34/// Version assignments for a single transaction
35#[derive(Debug, Clone, Default, PartialEq, Eq)]
36pub struct AssignedVersions {
37    pub shared_object_versions: Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
38    /// Versions of system objects, keyed by object ID, that this transaction may read during
39    /// execution but that are not part of its declared shared inputs. Each version is assigned
40    /// deterministically during consensus sequencing, so that every validator reads the same
41    /// version of the object.
42    ///
43    /// Today this holds at most the accumulator root version (as of the beginning of the consensus
44    /// commit this transaction belongs to). The accumulator root qualifies because it is written at
45    /// the end of every commit, so there is always a well-defined prior version to read from. More
46    /// system objects will be added over time.
47    pub system_object_versions: BTreeMap<ObjectID, SequenceNumber>,
48}
49
50impl AssignedVersions {
51    pub fn new(
52        shared_object_versions: Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
53        system_object_versions: BTreeMap<ObjectID, SequenceNumber>,
54    ) -> Self {
55        Self {
56            shared_object_versions,
57            system_object_versions,
58        }
59    }
60
61    /// Construct with only the accumulator root as the system object read during execution. The
62    /// accumulator root is the sole such object today; production callers build the full
63    /// `system_object_versions` map directly.
64    #[cfg(test)]
65    pub fn new_for_testing(
66        shared_object_versions: Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
67        accumulator_version: Option<SequenceNumber>,
68    ) -> Self {
69        Self::new(
70            shared_object_versions,
71            accumulator_version
72                .map(|v| (SUI_ACCUMULATOR_ROOT_OBJECT_ID, v))
73                .into_iter()
74                .collect(),
75        )
76    }
77
78    /// The accumulator root version this transaction reads, if any.
79    pub fn accumulator_version(&self) -> Option<SequenceNumber> {
80        self.system_object_versions
81            .get(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
82            .copied()
83    }
84
85    pub fn iter(&self) -> impl Iterator<Item = &(ConsensusObjectSequenceKey, SequenceNumber)> {
86        self.shared_object_versions.iter()
87    }
88
89    pub fn as_slice(&self) -> &[(ConsensusObjectSequenceKey, SequenceNumber)] {
90        &self.shared_object_versions
91    }
92}
93
94#[derive(Default, Debug, PartialEq, Eq)]
95pub struct AssignedTxAndVersions(pub Vec<(TransactionKey, AssignedVersions)>);
96
97impl AssignedTxAndVersions {
98    pub fn new(assigned_versions: Vec<(TransactionKey, AssignedVersions)>) -> Self {
99        Self(assigned_versions)
100    }
101
102    pub fn into_map(self) -> HashMap<TransactionKey, AssignedVersions> {
103        self.0.into_iter().collect()
104    }
105}
106
107/// A wrapper around things that can be scheduled for execution by the assigning of
108/// shared object versions.
109#[derive(Clone)]
110pub enum Schedulable<T = VerifiedExecutableTransaction> {
111    Transaction(T),
112    RandomnessStateUpdate(EpochId, RandomnessRound),
113    AccumulatorSettlement(EpochId, u64 /* checkpoint height */),
114    ConsensusCommitPrologue(EpochId, u64 /* round */, u32 /* sub_dag_index */),
115}
116
117impl From<VerifiedExecutableTransaction> for Schedulable<VerifiedExecutableTransaction> {
118    fn from(tx: VerifiedExecutableTransaction) -> Self {
119        Schedulable::Transaction(tx)
120    }
121}
122
123impl From<Schedulable<VerifiedExecutableTransactionWithAliases>>
124    for Schedulable<VerifiedExecutableTransaction>
125{
126    fn from(schedulable: Schedulable<VerifiedExecutableTransactionWithAliases>) -> Self {
127        match schedulable {
128            Schedulable::Transaction(tx) => Schedulable::Transaction(tx.into_tx()),
129            Schedulable::RandomnessStateUpdate(epoch, round) => {
130                Schedulable::RandomnessStateUpdate(epoch, round)
131            }
132            Schedulable::AccumulatorSettlement(epoch, checkpoint_height) => {
133                Schedulable::AccumulatorSettlement(epoch, checkpoint_height)
134            }
135            Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index) => {
136                Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index)
137            }
138        }
139    }
140}
141
142// AsTx is like Deref, in that it allows us to use either refs or values in Schedulable.
143// Deref does not work because it conflicts with the impl of Deref for VerifiedExecutableTransaction.
144pub trait AsTx {
145    fn as_tx(&self) -> &VerifiedExecutableTransaction;
146}
147
148impl AsTx for VerifiedExecutableTransaction {
149    fn as_tx(&self) -> &VerifiedExecutableTransaction {
150        self
151    }
152}
153
154impl AsTx for &'_ VerifiedExecutableTransaction {
155    fn as_tx(&self) -> &VerifiedExecutableTransaction {
156        self
157    }
158}
159
160impl AsTx for VerifiedExecutableTransactionWithAliases {
161    fn as_tx(&self) -> &VerifiedExecutableTransaction {
162        self.tx()
163    }
164}
165
166impl AsTx for &'_ VerifiedExecutableTransactionWithAliases {
167    fn as_tx(&self) -> &VerifiedExecutableTransaction {
168        self.tx()
169    }
170}
171
172impl Schedulable<&'_ VerifiedExecutableTransaction> {
173    // Cannot use the blanket ToOwned trait impl because it just calls clone.
174    pub fn to_owned_schedulable(&self) -> Schedulable<VerifiedExecutableTransaction> {
175        match self {
176            Schedulable::Transaction(tx) => Schedulable::Transaction((*tx).clone()),
177            Schedulable::RandomnessStateUpdate(epoch, round) => {
178                Schedulable::RandomnessStateUpdate(*epoch, *round)
179            }
180            Schedulable::AccumulatorSettlement(epoch, checkpoint_height) => {
181                Schedulable::AccumulatorSettlement(*epoch, *checkpoint_height)
182            }
183            Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index) => {
184                Schedulable::ConsensusCommitPrologue(*epoch, *round, *sub_dag_index)
185            }
186        }
187    }
188}
189
190impl<T> Schedulable<T> {
191    pub fn as_tx(&self) -> Option<&VerifiedExecutableTransaction>
192    where
193        T: AsTx,
194    {
195        match self {
196            Schedulable::Transaction(tx) => Some(tx.as_tx()),
197            Schedulable::RandomnessStateUpdate(_, _) => None,
198            Schedulable::AccumulatorSettlement(_, _) => None,
199            Schedulable::ConsensusCommitPrologue(_, _, _) => None,
200        }
201    }
202
203    pub fn shared_input_objects(
204        &self,
205        epoch_store: &AuthorityPerEpochStore,
206    ) -> impl Iterator<Item = SharedInputObject> + '_
207    where
208        T: AsTx,
209    {
210        match self {
211            Schedulable::Transaction(tx) => Either::Left(tx.as_tx().shared_input_objects()),
212            Schedulable::RandomnessStateUpdate(_, _) => {
213                Either::Right(std::iter::once(SharedInputObject {
214                    id: SUI_RANDOMNESS_STATE_OBJECT_ID,
215                    initial_shared_version: epoch_store
216                        .epoch_start_config()
217                        .randomness_obj_initial_shared_version()
218                        .expect("randomness obj initial shared version should be set"),
219                    mutability: SharedObjectMutability::Mutable,
220                }))
221            }
222            Schedulable::AccumulatorSettlement(_, _) => {
223                Either::Right(std::iter::once(SharedInputObject {
224                    id: SUI_ACCUMULATOR_ROOT_OBJECT_ID,
225                    initial_shared_version: epoch_store
226                        .epoch_start_config()
227                        .accumulator_root_obj_initial_shared_version()
228                        .expect("accumulator root obj initial shared version should be set"),
229                    mutability: SharedObjectMutability::Mutable,
230                }))
231            }
232            Schedulable::ConsensusCommitPrologue(_, _, _) => {
233                Either::Right(std::iter::once(SharedInputObject {
234                    id: SUI_CLOCK_OBJECT_ID,
235                    initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
236                    mutability: SharedObjectMutability::Mutable,
237                }))
238            }
239        }
240    }
241
242    pub fn non_shared_input_object_keys(&self) -> Vec<ObjectKey>
243    where
244        T: AsTx,
245    {
246        match self {
247            Schedulable::Transaction(tx) => transaction_non_shared_input_object_keys(tx.as_tx())
248                .expect("Transaction input should have been verified"),
249            Schedulable::RandomnessStateUpdate(_, _) => vec![],
250            Schedulable::AccumulatorSettlement(_, _) => vec![],
251            Schedulable::ConsensusCommitPrologue(_, _, _) => vec![],
252        }
253    }
254
255    pub fn receiving_object_keys(&self) -> Vec<ObjectKey>
256    where
257        T: AsTx,
258    {
259        match self {
260            Schedulable::Transaction(tx) => transaction_receiving_object_keys(tx.as_tx()),
261            Schedulable::RandomnessStateUpdate(_, _) => vec![],
262            Schedulable::AccumulatorSettlement(_, _) => vec![],
263            Schedulable::ConsensusCommitPrologue(_, _, _) => vec![],
264        }
265    }
266
267    pub fn key(&self) -> TransactionKey
268    where
269        T: AsTx,
270    {
271        match self {
272            Schedulable::Transaction(tx) => tx.as_tx().key(),
273            Schedulable::RandomnessStateUpdate(epoch, round) => {
274                TransactionKey::RandomnessRound(*epoch, *round)
275            }
276            Schedulable::AccumulatorSettlement(epoch, checkpoint_height) => {
277                TransactionKey::AccumulatorSettlement(*epoch, *checkpoint_height)
278            }
279            Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index) => {
280                TransactionKey::ConsensusCommitPrologue(*epoch, *round, *sub_dag_index)
281            }
282        }
283    }
284}
285
286#[must_use]
287#[derive(Default, Eq, PartialEq, Debug)]
288pub struct ConsensusSharedObjVerAssignment {
289    pub shared_input_next_versions: HashMap<ConsensusObjectSequenceKey, SequenceNumber>,
290    pub assigned_versions: AssignedTxAndVersions,
291}
292
293impl SharedObjVerManager {
294    pub fn assign_versions_from_consensus<'a, T>(
295        epoch_store: &AuthorityPerEpochStore,
296        cache_reader: &dyn ObjectCacheRead,
297        assignables: impl Iterator<Item = &'a Schedulable<T>> + Clone,
298        cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
299    ) -> SuiResult<ConsensusSharedObjVerAssignment>
300    where
301        T: AsTx + 'a,
302    {
303        let mut shared_input_next_versions = get_or_init_versions(
304            assignables
305                .clone()
306                .flat_map(|a| a.shared_input_objects(epoch_store)),
307            epoch_store,
308            cache_reader,
309        )?;
310        let mut assigned_versions = Vec::new();
311        for assignable in assignables {
312            assert!(
313                !matches!(assignable, Schedulable::AccumulatorSettlement(_, _))
314                    || epoch_store.accumulators_enabled(),
315                "AccumulatorSettlement should not be scheduled when accumulators are disabled"
316            );
317
318            let cert_assigned_versions = Self::assign_versions_for_certificate(
319                epoch_store,
320                assignable,
321                &mut shared_input_next_versions,
322                cancelled_txns,
323            );
324            assigned_versions.push((assignable.key(), cert_assigned_versions));
325        }
326
327        Ok(ConsensusSharedObjVerAssignment {
328            shared_input_next_versions,
329            assigned_versions: AssignedTxAndVersions::new(assigned_versions),
330        })
331    }
332
333    pub fn assign_versions_from_effects(
334        certs_and_effects: &[(
335            &VerifiedExecutableTransaction,
336            &TransactionEffects,
337            // Accumulator version
338            Option<SequenceNumber>,
339        )],
340        epoch_store: &AuthorityPerEpochStore,
341        cache_reader: &dyn ObjectCacheRead,
342    ) -> AssignedTxAndVersions {
343        // We don't care about the results since we can use effects to assign versions.
344        // But we must call it to make sure whenever a consensus object is touched the first time
345        // during an epoch, either through consensus or through checkpoint executor,
346        // its next version must be initialized. This is because we initialize the next version
347        // of a consensus object in an epoch by reading the current version from the object store.
348        // This must be done before we mutate it the first time, otherwise we would be initializing
349        // it with the wrong version.
350        let _ = get_or_init_versions(
351            certs_and_effects.iter().flat_map(|(cert, _, _)| {
352                cert.transaction_data().shared_input_objects().into_iter()
353            }),
354            epoch_store,
355            cache_reader,
356        );
357        let mut assigned_versions = Vec::new();
358        for (cert, effects, accumulator_version) in certs_and_effects {
359            let initial_version_map: BTreeMap<_, _> = cert
360                .transaction_data()
361                .shared_input_objects()
362                .into_iter()
363                .map(|input| input.into_id_and_version())
364                .collect();
365            let cert_assigned_versions: Vec<_> = effects
366                .input_consensus_objects()
367                .into_iter()
368                .map(|iso| {
369                    let (id, version) = iso.id_and_version();
370                    let initial_version = initial_version_map
371                        .get(&id)
372                        .expect("transaction must have all inputs from effects");
373                    ((id, *initial_version), version)
374                })
375                .collect();
376            let tx_key = cert.key();
377            trace!(
378                ?tx_key,
379                ?cert_assigned_versions,
380                "assigned consensus object versions from effects"
381            );
382            let system_object_versions: BTreeMap<ObjectID, SequenceNumber> = (*accumulator_version)
383                .map(|v| (SUI_ACCUMULATOR_ROOT_OBJECT_ID, v))
384                .into_iter()
385                .collect();
386            assigned_versions.push((
387                tx_key,
388                AssignedVersions::new(cert_assigned_versions, system_object_versions),
389            ));
390        }
391        AssignedTxAndVersions::new(assigned_versions)
392    }
393
394    pub fn assign_versions_for_certificate(
395        epoch_store: &AuthorityPerEpochStore,
396        assignable: &Schedulable<impl AsTx>,
397        shared_input_next_versions: &mut HashMap<ConsensusObjectSequenceKey, SequenceNumber>,
398        cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
399    ) -> AssignedVersions {
400        let shared_input_objects: Vec<_> = assignable.shared_input_objects(epoch_store).collect();
401
402        let accumulator_version = if epoch_store.accumulators_enabled() {
403            let accumulator_initial_version = epoch_store
404                .epoch_start_config()
405                .accumulator_root_obj_initial_shared_version()
406                .expect("accumulator root obj initial shared version should be set when accumulators are enabled");
407
408            let accumulator_version = *shared_input_next_versions
409                .get(&(SUI_ACCUMULATOR_ROOT_OBJECT_ID, accumulator_initial_version))
410                .expect("accumulator object must be in shared_input_next_versions when withdraws are enabled");
411
412            Some(accumulator_version)
413        } else {
414            None
415        };
416        // The accumulator root is the only system object read implicitly during execution today.
417        let system_object_versions: BTreeMap<ObjectID, SequenceNumber> = accumulator_version
418            .map(|v| (SUI_ACCUMULATOR_ROOT_OBJECT_ID, v))
419            .into_iter()
420            .collect();
421
422        if shared_input_objects.is_empty() {
423            // No shared object used by this transaction. No need to assign versions.
424            return AssignedVersions::new(vec![], system_object_versions);
425        }
426
427        let tx_key = assignable.key();
428
429        // Check if the transaction is cancelled due to congestion.
430        let cancellation_info = tx_key
431            .as_digest()
432            .and_then(|tx_digest| cancelled_txns.get(tx_digest));
433        let congested_objects_info: Option<HashSet<_>> =
434            if let Some(CancelConsensusCertificateReason::CongestionOnObjects(congested_objects)) =
435                &cancellation_info
436            {
437                Some(congested_objects.iter().cloned().collect())
438            } else {
439                None
440            };
441        let txn_cancelled = cancellation_info.is_some();
442
443        let mut input_object_keys = assignable.non_shared_input_object_keys();
444        let mut assigned_versions = Vec::with_capacity(shared_input_objects.len());
445        let mut is_exclusively_accessed_input = Vec::with_capacity(shared_input_objects.len());
446        // Record receiving object versions towards the shared version computation.
447        let receiving_object_keys = assignable.receiving_object_keys();
448        input_object_keys.extend(receiving_object_keys);
449
450        if txn_cancelled {
451            // For cancelled transaction due to congestion, assign special versions to all shared objects.
452            // Note that new lamport version does not depend on any shared objects.
453            for SharedInputObject {
454                id,
455                initial_shared_version,
456                ..
457            } in shared_input_objects.iter()
458            {
459                let assigned_version = match cancellation_info {
460                    Some(CancelConsensusCertificateReason::CongestionOnObjects(_)) => {
461                        if congested_objects_info
462                            .as_ref()
463                            .is_some_and(|info| info.contains(id))
464                        {
465                            SequenceNumber::CONGESTED
466                        } else {
467                            SequenceNumber::CANCELLED_READ
468                        }
469                    }
470                    Some(CancelConsensusCertificateReason::DkgFailed) => {
471                        if id == &SUI_RANDOMNESS_STATE_OBJECT_ID {
472                            SequenceNumber::RANDOMNESS_UNAVAILABLE
473                        } else {
474                            SequenceNumber::CANCELLED_READ
475                        }
476                    }
477                    None => unreachable!("cancelled transaction should have cancellation info"),
478                };
479                assigned_versions.push(((*id, *initial_shared_version), assigned_version));
480                is_exclusively_accessed_input.push(false);
481            }
482        } else {
483            for (
484                SharedInputObject {
485                    id,
486                    initial_shared_version,
487                    mutability,
488                },
489                assigned_version,
490            ) in shared_input_objects.iter().map(|obj| {
491                (
492                    obj,
493                    *shared_input_next_versions
494                        .get(&obj.id_and_version())
495                        .unwrap(),
496                )
497            }) {
498                assigned_versions.push(((*id, *initial_shared_version), assigned_version));
499                input_object_keys.push(ObjectKey(*id, assigned_version));
500                is_exclusively_accessed_input.push(mutability.is_exclusive());
501            }
502        }
503
504        let next_version =
505            SequenceNumber::lamport_increment(input_object_keys.iter().map(|obj| obj.1));
506        assert!(
507            next_version.is_valid(),
508            "Assigned version must be valid. Got {:?}",
509            next_version
510        );
511
512        if !txn_cancelled {
513            // Update the next version for the shared objects.
514            assigned_versions
515                .iter()
516                .zip_debug_eq(is_exclusively_accessed_input)
517                .filter_map(|((id, _), mutable)| {
518                    if mutable {
519                        Some((*id, next_version))
520                    } else {
521                        None
522                    }
523                })
524                .for_each(|(id, version)| {
525                    assert!(
526                        version.is_valid(),
527                        "Assigned version must be a valid version."
528                    );
529                    shared_input_next_versions
530                        .insert(id, version)
531                        .expect("Object must exist in shared_input_next_versions.");
532                });
533        }
534
535        trace!(
536            ?tx_key,
537            ?assigned_versions,
538            ?next_version,
539            ?txn_cancelled,
540            "locking shared objects"
541        );
542
543        AssignedVersions::new(assigned_versions, system_object_versions)
544    }
545}
546
547fn get_or_init_versions<'a>(
548    shared_input_objects: impl Iterator<Item = SharedInputObject> + 'a,
549    epoch_store: &AuthorityPerEpochStore,
550    cache_reader: &dyn ObjectCacheRead,
551) -> SuiResult<HashMap<ConsensusObjectSequenceKey, SequenceNumber>> {
552    let mut shared_input_objects: Vec<_> = shared_input_objects
553        .map(|so| so.into_id_and_version())
554        .collect();
555
556    if epoch_store.accumulators_enabled() {
557        shared_input_objects.push((
558            SUI_ACCUMULATOR_ROOT_OBJECT_ID,
559            epoch_store
560                .epoch_start_config()
561                .accumulator_root_obj_initial_shared_version()
562                .expect("accumulator root obj initial shared version should be set"),
563        ));
564    }
565
566    shared_input_objects.sort();
567    shared_input_objects.dedup();
568
569    epoch_store.get_or_init_next_object_versions(&shared_input_objects, cache_reader)
570}
571
572#[cfg(test)]
573mod tests {
574    use super::*;
575
576    use crate::authority::AuthorityState;
577    use crate::authority::shared_object_version_manager::{
578        ConsensusSharedObjVerAssignment, SharedObjVerManager,
579    };
580    use crate::authority::test_authority_builder::TestAuthorityBuilder;
581    use std::collections::{BTreeMap, HashMap};
582    use std::sync::Arc;
583    use sui_protocol_config::ProtocolConfig;
584    use sui_test_transaction_builder::TestTransactionBuilder;
585    use sui_types::base_types::{ObjectID, SequenceNumber, SuiAddress};
586    use sui_types::crypto::{RandomnessRound, get_account_key_pair};
587    use sui_types::digests::ObjectDigest;
588    use sui_types::effects::TestEffectsBuilder;
589    use sui_types::executable_transaction::{
590        CertificateProof, ExecutableTransaction, VerifiedExecutableTransaction,
591    };
592
593    use sui_types::object::Object;
594    use sui_types::transaction::{ObjectArg, SenderSignedData, VerifiedTransaction};
595
596    use sui_types::gas_coin::GAS;
597    use sui_types::transaction::FundsWithdrawalArg;
598    use sui_types::{SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID};
599
600    #[tokio::test]
601    async fn test_assign_versions_from_consensus_basic() {
602        let shared_object = Object::shared_for_testing();
603        let id = shared_object.id();
604        let init_shared_version = shared_object.owner.start_version().unwrap();
605        let authority = TestAuthorityBuilder::new()
606            .with_starting_objects(std::slice::from_ref(&shared_object))
607            .build()
608            .await;
609        let certs = [
610            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 3),
611            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, false)], 5),
612            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 9),
613            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 11),
614        ];
615        let epoch_store = authority.epoch_store_for_testing();
616        let assignables = certs
617            .iter()
618            .map(Schedulable::Transaction)
619            .collect::<Vec<_>>();
620        let ConsensusSharedObjVerAssignment {
621            shared_input_next_versions,
622            assigned_versions,
623        } = SharedObjVerManager::assign_versions_from_consensus(
624            &epoch_store,
625            authority.get_object_cache_reader().as_ref(),
626            assignables.iter(),
627            &BTreeMap::new(),
628        )
629        .unwrap();
630        // Check that the shared object's next version is always initialized in the epoch store.
631        assert_eq!(
632            epoch_store
633                .get_next_object_version(&id, init_shared_version)
634                .unwrap(),
635            init_shared_version
636        );
637        // Check that the final version of the shared object is the lamport version of the last
638        // transaction.
639        assert_eq!(
640            *shared_input_next_versions
641                .get(&(id, init_shared_version))
642                .unwrap(),
643            SequenceNumber::from_u64(12)
644        );
645        // Check that the version assignment for each transaction is correct.
646        // For a transaction that uses the shared object with mutable=false, it won't update the version
647        // using lamport version, hence the next transaction will use the same version number.
648        // In the following case, certs[2] has the same assignment as certs[1] for this reason.
649        let expected_accumulator_version = SequenceNumber::from_u64(1);
650        assert_eq!(
651            assigned_versions.0,
652            vec![
653                (
654                    certs[0].key(),
655                    AssignedVersions::new_for_testing(
656                        vec![((id, init_shared_version), init_shared_version)],
657                        Some(expected_accumulator_version)
658                    )
659                ),
660                (
661                    certs[1].key(),
662                    AssignedVersions::new_for_testing(
663                        vec![((id, init_shared_version), SequenceNumber::from_u64(4))],
664                        Some(expected_accumulator_version)
665                    )
666                ),
667                (
668                    certs[2].key(),
669                    AssignedVersions::new_for_testing(
670                        vec![((id, init_shared_version), SequenceNumber::from_u64(4))],
671                        Some(expected_accumulator_version)
672                    )
673                ),
674                (
675                    certs[3].key(),
676                    AssignedVersions::new_for_testing(
677                        vec![((id, init_shared_version), SequenceNumber::from_u64(10))],
678                        Some(expected_accumulator_version)
679                    )
680                ),
681            ]
682        );
683    }
684
685    #[tokio::test]
686    async fn test_assign_versions_from_consensus_with_randomness() {
687        let authority = TestAuthorityBuilder::new().build().await;
688        let epoch_store = authority.epoch_store_for_testing();
689        let randomness_obj_version = epoch_store
690            .epoch_start_config()
691            .randomness_obj_initial_shared_version()
692            .unwrap();
693        let certs = [
694            VerifiedExecutableTransaction::new_system(
695                VerifiedTransaction::new_randomness_state_update(
696                    epoch_store.epoch(),
697                    RandomnessRound::new(1),
698                    vec![],
699                    randomness_obj_version,
700                ),
701                epoch_store.epoch(),
702            ),
703            generate_shared_objs_tx_with_gas_version(
704                &[(
705                    SUI_RANDOMNESS_STATE_OBJECT_ID,
706                    randomness_obj_version,
707                    // This can only be false since it's not allowed to use randomness object with mutable=true.
708                    false,
709                )],
710                3,
711            ),
712            generate_shared_objs_tx_with_gas_version(
713                &[(
714                    SUI_RANDOMNESS_STATE_OBJECT_ID,
715                    randomness_obj_version,
716                    false,
717                )],
718                5,
719            ),
720        ];
721        let assignables = certs
722            .iter()
723            .map(Schedulable::Transaction)
724            .collect::<Vec<_>>();
725        let ConsensusSharedObjVerAssignment {
726            shared_input_next_versions,
727            assigned_versions,
728        } = SharedObjVerManager::assign_versions_from_consensus(
729            &epoch_store,
730            authority.get_object_cache_reader().as_ref(),
731            assignables.iter(),
732            &BTreeMap::new(),
733        )
734        .unwrap();
735        // Check that the randomness object's next version is initialized.
736        assert_eq!(
737            epoch_store
738                .get_next_object_version(&SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version)
739                .unwrap(),
740            randomness_obj_version
741        );
742        let next_randomness_obj_version = randomness_obj_version.next();
743        assert_eq!(
744            *shared_input_next_versions
745                .get(&(SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version))
746                .unwrap(),
747            // Randomness object's version is only incremented by 1 regardless of lamport version.
748            next_randomness_obj_version
749        );
750        let expected_accumulator_version = SequenceNumber::from_u64(1);
751        assert_eq!(
752            assigned_versions.0,
753            vec![
754                (
755                    certs[0].key(),
756                    AssignedVersions::new_for_testing(
757                        vec![(
758                            (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
759                            randomness_obj_version
760                        )],
761                        Some(expected_accumulator_version)
762                    )
763                ),
764                (
765                    certs[1].key(),
766                    // It is critical that the randomness object version is updated before the assignment.
767                    AssignedVersions::new_for_testing(
768                        vec![(
769                            (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
770                            next_randomness_obj_version
771                        )],
772                        Some(expected_accumulator_version)
773                    )
774                ),
775                (
776                    certs[2].key(),
777                    // It is critical that the randomness object version is updated before the assignment.
778                    AssignedVersions::new_for_testing(
779                        vec![(
780                            (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
781                            next_randomness_obj_version
782                        )],
783                        Some(expected_accumulator_version)
784                    )
785                ),
786            ]
787        );
788    }
789
790    // Tests shared object version assignment for cancelled transaction.
791    #[tokio::test]
792    async fn test_assign_versions_from_consensus_with_cancellation() {
793        let shared_object_1 = Object::shared_for_testing();
794        let shared_object_2 = Object::shared_for_testing();
795        let id1 = shared_object_1.id();
796        let id2 = shared_object_2.id();
797        let init_shared_version_1 = shared_object_1.owner.start_version().unwrap();
798        let init_shared_version_2 = shared_object_2.owner.start_version().unwrap();
799        let authority = TestAuthorityBuilder::new()
800            .with_starting_objects(&[shared_object_1.clone(), shared_object_2.clone()])
801            .build()
802            .await;
803        let randomness_obj_version = authority
804            .epoch_store_for_testing()
805            .epoch_start_config()
806            .randomness_obj_initial_shared_version()
807            .unwrap();
808
809        // Generate 5 transactions for testing.
810        //   tx1: shared_object_1, shared_object_2, owned_object_version = 3
811        //   tx2: shared_object_1, shared_object_2, owned_object_version = 5
812        //   tx3: shared_object_1, owned_object_version = 1
813        //   tx4: shared_object_1, shared_object_2, owned_object_version = 9
814        //   tx5: shared_object_1, shared_object_2, owned_object_version = 11
815        //
816        // Later, we cancel transaction 2 and 4 due to congestion, and 5 due to DKG failure.
817        // Expected outcome:
818        //   tx1: both shared objects assign version 1, lamport version = 4
819        //   tx2: shared objects assign cancelled version, lamport version = 6 due to gas object version = 5
820        //   tx3: shared object 1 assign version 4, lamport version = 5
821        //   tx4: shared objects assign cancelled version, lamport version = 10 due to gas object version = 9
822        //   tx5: shared objects assign cancelled version, lamport version = 12 due to gas object version = 11
823        let certs = [
824            generate_shared_objs_tx_with_gas_version(
825                &[
826                    (id1, init_shared_version_1, true),
827                    (id2, init_shared_version_2, true),
828                ],
829                3,
830            ),
831            generate_shared_objs_tx_with_gas_version(
832                &[
833                    (id1, init_shared_version_1, true),
834                    (id2, init_shared_version_2, true),
835                ],
836                5,
837            ),
838            generate_shared_objs_tx_with_gas_version(&[(id1, init_shared_version_1, true)], 1),
839            generate_shared_objs_tx_with_gas_version(
840                &[
841                    (id1, init_shared_version_1, true),
842                    (id2, init_shared_version_2, true),
843                ],
844                9,
845            ),
846            generate_shared_objs_tx_with_gas_version(
847                &[
848                    (
849                        SUI_RANDOMNESS_STATE_OBJECT_ID,
850                        randomness_obj_version,
851                        false,
852                    ),
853                    (id2, init_shared_version_2, true),
854                ],
855                11,
856            ),
857        ];
858        let epoch_store = authority.epoch_store_for_testing();
859
860        // Cancel transactions 2 and 4 due to congestion.
861        let cancelled_txns: BTreeMap<TransactionDigest, CancelConsensusCertificateReason> = [
862            (
863                *certs[1].digest(),
864                CancelConsensusCertificateReason::CongestionOnObjects(vec![id1]),
865            ),
866            (
867                *certs[3].digest(),
868                CancelConsensusCertificateReason::CongestionOnObjects(vec![id2]),
869            ),
870            (
871                *certs[4].digest(),
872                CancelConsensusCertificateReason::DkgFailed,
873            ),
874        ]
875        .into_iter()
876        .collect();
877
878        let assignables = certs
879            .iter()
880            .map(Schedulable::Transaction)
881            .collect::<Vec<_>>();
882
883        // Run version assignment logic.
884        let ConsensusSharedObjVerAssignment {
885            mut shared_input_next_versions,
886            assigned_versions,
887        } = SharedObjVerManager::assign_versions_from_consensus(
888            &epoch_store,
889            authority.get_object_cache_reader().as_ref(),
890            assignables.iter(),
891            &cancelled_txns,
892        )
893        .unwrap();
894
895        // Check that the final version of the shared object is the lamport version of the last
896        // transaction.
897        shared_input_next_versions
898            .remove(&(SUI_ACCUMULATOR_ROOT_OBJECT_ID, SequenceNumber::from_u64(1)));
899        assert_eq!(
900            shared_input_next_versions,
901            HashMap::from([
902                ((id1, init_shared_version_1), SequenceNumber::from_u64(5)), // determined by tx3
903                ((id2, init_shared_version_2), SequenceNumber::from_u64(4)), // determined by tx1
904                (
905                    (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
906                    SequenceNumber::from_u64(1)
907                ), // not mutable
908            ])
909        );
910
911        // Check that the version assignment for each transaction is correct.
912        let expected_accumulator_version = SequenceNumber::from_u64(1);
913        assert_eq!(
914            assigned_versions.0,
915            vec![
916                (
917                    certs[0].key(),
918                    AssignedVersions::new_for_testing(
919                        vec![
920                            ((id1, init_shared_version_1), init_shared_version_1),
921                            ((id2, init_shared_version_2), init_shared_version_2)
922                        ],
923                        Some(expected_accumulator_version)
924                    )
925                ),
926                (
927                    certs[1].key(),
928                    AssignedVersions::new_for_testing(
929                        vec![
930                            ((id1, init_shared_version_1), SequenceNumber::CONGESTED),
931                            ((id2, init_shared_version_2), SequenceNumber::CANCELLED_READ),
932                        ],
933                        Some(expected_accumulator_version)
934                    )
935                ),
936                (
937                    certs[2].key(),
938                    AssignedVersions::new_for_testing(
939                        vec![((id1, init_shared_version_1), SequenceNumber::from_u64(4))],
940                        Some(expected_accumulator_version)
941                    )
942                ),
943                (
944                    certs[3].key(),
945                    AssignedVersions::new_for_testing(
946                        vec![
947                            ((id1, init_shared_version_1), SequenceNumber::CANCELLED_READ),
948                            ((id2, init_shared_version_2), SequenceNumber::CONGESTED)
949                        ],
950                        Some(expected_accumulator_version)
951                    )
952                ),
953                (
954                    certs[4].key(),
955                    AssignedVersions::new_for_testing(
956                        vec![
957                            (
958                                (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
959                                SequenceNumber::RANDOMNESS_UNAVAILABLE
960                            ),
961                            ((id2, init_shared_version_2), SequenceNumber::CANCELLED_READ)
962                        ],
963                        Some(expected_accumulator_version)
964                    )
965                ),
966            ]
967        );
968    }
969
970    #[tokio::test]
971    async fn test_assign_versions_from_effects() {
972        let shared_object = Object::shared_for_testing();
973        let id = shared_object.id();
974        let init_shared_version = shared_object.owner.start_version().unwrap();
975        let authority = TestAuthorityBuilder::new()
976            .with_starting_objects(std::slice::from_ref(&shared_object))
977            .build()
978            .await;
979        let certs = [
980            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 3),
981            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, false)], 5),
982            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 9),
983            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 11),
984        ];
985        let effects = [
986            TestEffectsBuilder::new(certs[0].data()).build(),
987            TestEffectsBuilder::new(certs[1].data())
988                .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(4))]))
989                .build(),
990            TestEffectsBuilder::new(certs[2].data())
991                .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(4))]))
992                .build(),
993            TestEffectsBuilder::new(certs[3].data())
994                .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(10))]))
995                .build(),
996        ];
997        let epoch_store = authority.epoch_store_for_testing();
998        let assigned_versions = SharedObjVerManager::assign_versions_from_effects(
999            certs
1000                .iter()
1001                .zip_debug_eq(effects.iter())
1002                .map(|(cert, effect)| (cert, effect, None))
1003                .collect::<Vec<_>>()
1004                .as_slice(),
1005            &epoch_store,
1006            authority.get_object_cache_reader().as_ref(),
1007        );
1008        // Check that the shared object's next version is always initialized in the epoch store.
1009        assert_eq!(
1010            epoch_store
1011                .get_next_object_version(&id, init_shared_version)
1012                .unwrap(),
1013            init_shared_version
1014        );
1015        assert_eq!(
1016            assigned_versions.0,
1017            vec![
1018                (
1019                    certs[0].key(),
1020                    AssignedVersions::new_for_testing(
1021                        vec![((id, init_shared_version), init_shared_version)],
1022                        None
1023                    )
1024                ),
1025                (
1026                    certs[1].key(),
1027                    AssignedVersions::new_for_testing(
1028                        vec![((id, init_shared_version), SequenceNumber::from_u64(4))],
1029                        None
1030                    )
1031                ),
1032                (
1033                    certs[2].key(),
1034                    AssignedVersions::new_for_testing(
1035                        vec![((id, init_shared_version), SequenceNumber::from_u64(4))],
1036                        None
1037                    )
1038                ),
1039                (
1040                    certs[3].key(),
1041                    AssignedVersions::new_for_testing(
1042                        vec![((id, init_shared_version), SequenceNumber::from_u64(10))],
1043                        None
1044                    )
1045                ),
1046            ]
1047        );
1048    }
1049
1050    /// Generate a transaction that uses shared objects as specified in the parameters.
1051    /// Also uses a gas object with specified version.
1052    /// The version of the gas object is used to manipulate the lamport version of this transaction.
1053    fn generate_shared_objs_tx_with_gas_version(
1054        shared_objects: &[(ObjectID, SequenceNumber, bool)],
1055        gas_object_version: u64,
1056    ) -> VerifiedExecutableTransaction {
1057        let mut tx_builder = TestTransactionBuilder::new(
1058            SuiAddress::ZERO,
1059            (
1060                ObjectID::random(),
1061                SequenceNumber::from_u64(gas_object_version),
1062                ObjectDigest::random(),
1063            ),
1064            0,
1065        );
1066        let tx_data = {
1067            let builder = tx_builder.ptb_builder_mut();
1068            for (shared_object_id, shared_object_init_version, shared_object_mutable) in
1069                shared_objects
1070            {
1071                builder
1072                    .obj(ObjectArg::SharedObject {
1073                        id: *shared_object_id,
1074                        initial_shared_version: *shared_object_init_version,
1075                        mutability: if *shared_object_mutable {
1076                            SharedObjectMutability::Mutable
1077                        } else {
1078                            SharedObjectMutability::Immutable
1079                        },
1080                    })
1081                    .unwrap();
1082            }
1083            tx_builder.build()
1084        };
1085        let tx = SenderSignedData::new(tx_data, vec![]);
1086        VerifiedExecutableTransaction::new_unchecked(ExecutableTransaction::new_from_data_and_sig(
1087            tx,
1088            CertificateProof::new_system(0),
1089        ))
1090    }
1091
1092    struct WithdrawTestContext {
1093        authority: Arc<AuthorityState>,
1094        assignables: Vec<Schedulable<VerifiedExecutableTransaction>>,
1095        shared_objects: Vec<Object>,
1096    }
1097
1098    impl WithdrawTestContext {
1099        pub async fn new() -> Self {
1100            // Create a shared object for testing
1101            let shared_objects = vec![Object::shared_for_testing()];
1102            let mut config = ProtocolConfig::get_for_max_version_UNSAFE();
1103            config.set_enable_accumulators_for_testing(true);
1104            let authority = TestAuthorityBuilder::new()
1105                .with_starting_objects(&shared_objects)
1106                .with_protocol_config(config)
1107                .build()
1108                .await;
1109            Self {
1110                authority,
1111                assignables: vec![],
1112                shared_objects,
1113            }
1114        }
1115
1116        pub fn add_withdraw_transaction(&mut self) -> TransactionKey {
1117            // Generate random sender and gas object for each transaction
1118            let (sender, keypair) = get_account_key_pair();
1119            let gas_object = Object::with_owner_for_testing(sender);
1120            let gas_object_ref = gas_object.compute_object_reference();
1121            // Generate a unique gas price to make the transaction unique.
1122            let gas_price = (self.assignables.len() + 1) as u64;
1123            let mut tx_builder = TestTransactionBuilder::new(sender, gas_object_ref, gas_price);
1124            let tx_data = {
1125                let ptb_builder = tx_builder.ptb_builder_mut();
1126                ptb_builder
1127                    .funds_withdrawal(FundsWithdrawalArg::balance_from_sender(
1128                        200,
1129                        GAS::type_tag(),
1130                    ))
1131                    .unwrap();
1132                tx_builder.build()
1133            };
1134            let cert = VerifiedExecutableTransaction::new_for_testing(tx_data, &keypair);
1135            let key = cert.key();
1136            self.assignables.push(Schedulable::Transaction(cert));
1137            key
1138        }
1139
1140        pub fn add_settlement_transaction(&mut self) -> TransactionKey {
1141            let height = (self.assignables.len() + 1) as u64;
1142            let settlement = Schedulable::AccumulatorSettlement(0, height);
1143            let key = settlement.key();
1144            self.assignables.push(settlement);
1145            key
1146        }
1147
1148        pub fn add_withdraw_with_shared_object_transaction(&mut self) -> TransactionKey {
1149            // Generate random sender and gas object for each transaction
1150            let (sender, keypair) = get_account_key_pair();
1151            let gas_object = Object::with_owner_for_testing(sender);
1152            let gas_object_ref = gas_object.compute_object_reference();
1153            // Generate a unique gas price to make the transaction unique.
1154            let gas_price = (self.assignables.len() + 1) as u64;
1155            let mut tx_builder = TestTransactionBuilder::new(sender, gas_object_ref, gas_price);
1156            let tx_data = {
1157                let ptb_builder = tx_builder.ptb_builder_mut();
1158                // Add shared object to the transaction
1159                if let Some(shared_obj) = self.shared_objects.first() {
1160                    let id = shared_obj.id();
1161                    let init_version = shared_obj.owner.start_version().unwrap();
1162                    ptb_builder
1163                        .obj(ObjectArg::SharedObject {
1164                            id,
1165                            initial_shared_version: init_version,
1166                            mutability: SharedObjectMutability::Mutable,
1167                        })
1168                        .unwrap();
1169                }
1170                // Add balance withdraw
1171                ptb_builder
1172                    .funds_withdrawal(FundsWithdrawalArg::balance_from_sender(
1173                        200,
1174                        GAS::type_tag(),
1175                    ))
1176                    .unwrap();
1177                tx_builder.build()
1178            };
1179            let cert = VerifiedExecutableTransaction::new_for_testing(tx_data, &keypair);
1180            let key = cert.key();
1181            self.assignables.push(Schedulable::Transaction(cert));
1182            key
1183        }
1184
1185        pub fn assign_versions_from_consensus(&self) -> ConsensusSharedObjVerAssignment {
1186            let epoch_store = self.authority.epoch_store_for_testing();
1187            SharedObjVerManager::assign_versions_from_consensus(
1188                &epoch_store,
1189                self.authority.get_object_cache_reader().as_ref(),
1190                self.assignables.iter(),
1191                &BTreeMap::new(),
1192            )
1193            .unwrap()
1194        }
1195    }
1196
1197    #[tokio::test]
1198    async fn test_assign_versions_from_consensus_with_withdraws_simple() {
1199        // Note that we don't need a shared object to trigger withdraw version assignment.
1200        // In fact it is important that this works without a shared object.
1201        let mut ctx = WithdrawTestContext::new().await;
1202
1203        let acc_version = ctx
1204            .authority
1205            .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1206            .unwrap()
1207            .version();
1208
1209        let withdraw_key = ctx.add_withdraw_transaction();
1210        let settlement_key = ctx.add_settlement_transaction();
1211
1212        let assigned_versions = ctx.assign_versions_from_consensus();
1213        assert_eq!(
1214            assigned_versions,
1215            ConsensusSharedObjVerAssignment {
1216                assigned_versions: AssignedTxAndVersions::new(vec![
1217                    (
1218                        withdraw_key,
1219                        AssignedVersions::new_for_testing(vec![], Some(acc_version))
1220                    ),
1221                    (
1222                        settlement_key,
1223                        AssignedVersions::new_for_testing(
1224                            vec![((SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version), acc_version)],
1225                            Some(acc_version)
1226                        )
1227                    ),
1228                ]),
1229                shared_input_next_versions: HashMap::from([(
1230                    (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1231                    acc_version.next()
1232                )]),
1233            }
1234        );
1235    }
1236
1237    #[tokio::test]
1238    async fn test_assign_versions_from_consensus_with_multiple_withdraws_and_settlements() {
1239        // Test with multiple withdrawals and multiple settlements, with settlement as the last transaction
1240        let mut ctx = WithdrawTestContext::new().await;
1241
1242        let acc_version = ctx
1243            .authority
1244            .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1245            .unwrap()
1246            .version();
1247
1248        // First withdrawal and settlement
1249        let withdraw_key1 = ctx.add_withdraw_transaction();
1250        let settlement_key1 = ctx.add_settlement_transaction();
1251
1252        // Second withdrawal and settlement
1253        let withdraw_key2 = ctx.add_withdraw_transaction();
1254        let settlement_key2 = ctx.add_settlement_transaction();
1255
1256        // Third withdrawal and final settlement
1257        let withdraw_key3 = ctx.add_withdraw_transaction();
1258        let settlement_key3 = ctx.add_settlement_transaction();
1259
1260        let assigned_versions = ctx.assign_versions_from_consensus();
1261        assert_eq!(
1262            assigned_versions,
1263            ConsensusSharedObjVerAssignment {
1264                assigned_versions: AssignedTxAndVersions::new(vec![
1265                    (
1266                        withdraw_key1,
1267                        AssignedVersions::new_for_testing(vec![], Some(acc_version))
1268                    ),
1269                    (
1270                        settlement_key1,
1271                        AssignedVersions::new_for_testing(
1272                            vec![((SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version), acc_version)],
1273                            Some(acc_version)
1274                        )
1275                    ),
1276                    (
1277                        withdraw_key2,
1278                        AssignedVersions::new_for_testing(vec![], Some(acc_version.next()))
1279                    ),
1280                    (
1281                        settlement_key2,
1282                        AssignedVersions::new_for_testing(
1283                            vec![(
1284                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1285                                acc_version.next()
1286                            )],
1287                            Some(acc_version.next())
1288                        )
1289                    ),
1290                    (
1291                        withdraw_key3,
1292                        AssignedVersions::new_for_testing(vec![], Some(acc_version.next().next()))
1293                    ),
1294                    (
1295                        settlement_key3,
1296                        AssignedVersions::new_for_testing(
1297                            vec![(
1298                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1299                                acc_version.next().next()
1300                            )],
1301                            Some(acc_version.next().next())
1302                        )
1303                    ),
1304                ]),
1305                shared_input_next_versions: HashMap::from([(
1306                    (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1307                    acc_version.next().next().next()
1308                )]),
1309            }
1310        );
1311    }
1312
1313    #[tokio::test]
1314    async fn test_assign_versions_from_consensus_with_withdraw_and_shared_object() {
1315        // Test that a transaction can have both a withdrawal and use a shared object
1316        let mut ctx = WithdrawTestContext::new().await;
1317
1318        // Get the shared object info from the context
1319        let shared_obj_id = ctx.shared_objects[0].id();
1320        let shared_obj_version = ctx.shared_objects[0].owner.start_version().unwrap();
1321
1322        let acc_version = ctx
1323            .authority
1324            .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1325            .unwrap()
1326            .version();
1327
1328        let withdraw_with_shared_key = ctx.add_withdraw_with_shared_object_transaction();
1329        let settlement_key = ctx.add_settlement_transaction();
1330
1331        let assigned_versions = ctx.assign_versions_from_consensus();
1332        assert_eq!(
1333            assigned_versions,
1334            ConsensusSharedObjVerAssignment {
1335                assigned_versions: AssignedTxAndVersions::new(vec![
1336                    (
1337                        withdraw_with_shared_key,
1338                        AssignedVersions::new_for_testing(
1339                            vec![((shared_obj_id, shared_obj_version), shared_obj_version)],
1340                            Some(acc_version)
1341                        )
1342                    ),
1343                    (
1344                        settlement_key,
1345                        AssignedVersions::new_for_testing(
1346                            vec![((SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version), acc_version)],
1347                            Some(acc_version)
1348                        )
1349                    ),
1350                ]),
1351                shared_input_next_versions: HashMap::from([
1352                    (
1353                        (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1354                        acc_version.next()
1355                    ),
1356                    (
1357                        (shared_obj_id, shared_obj_version),
1358                        shared_obj_version.next()
1359                    ),
1360                ]),
1361            }
1362        );
1363    }
1364}