sui_core/authority/
shared_object_version_manager.rs

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