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    if epoch_store.accumulators_enabled() {
519        shared_input_objects.push((
520            SUI_ACCUMULATOR_ROOT_OBJECT_ID,
521            epoch_store
522                .epoch_start_config()
523                .accumulator_root_obj_initial_shared_version()
524                .expect("accumulator root obj initial shared version should be set"),
525        ));
526    }
527
528    shared_input_objects.sort();
529    shared_input_objects.dedup();
530
531    epoch_store.get_or_init_next_object_versions(&shared_input_objects, cache_reader)
532}
533
534#[cfg(test)]
535mod tests {
536    use super::*;
537
538    use crate::authority::AuthorityState;
539    use crate::authority::epoch_start_configuration::EpochStartConfigTrait;
540    use crate::authority::shared_object_version_manager::{
541        ConsensusSharedObjVerAssignment, SharedObjVerManager,
542    };
543    use crate::authority::test_authority_builder::TestAuthorityBuilder;
544    use std::collections::{BTreeMap, HashMap};
545    use std::sync::Arc;
546    use sui_protocol_config::ProtocolConfig;
547    use sui_test_transaction_builder::TestTransactionBuilder;
548    use sui_types::base_types::{ObjectID, SequenceNumber, SuiAddress};
549    use sui_types::crypto::{RandomnessRound, get_account_key_pair};
550    use sui_types::digests::ObjectDigest;
551    use sui_types::effects::TestEffectsBuilder;
552    use sui_types::executable_transaction::{
553        CertificateProof, ExecutableTransaction, VerifiedExecutableTransaction,
554    };
555
556    use sui_types::object::Object;
557    use sui_types::transaction::{ObjectArg, SenderSignedData, VerifiedTransaction};
558
559    use sui_types::gas_coin::GAS;
560    use sui_types::transaction::FundsWithdrawalArg;
561    use sui_types::{SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID};
562
563    #[tokio::test]
564    async fn test_assign_versions_from_consensus_basic() {
565        let shared_object = Object::shared_for_testing();
566        let id = shared_object.id();
567        let init_shared_version = shared_object.owner.start_version().unwrap();
568        let authority = TestAuthorityBuilder::new()
569            .with_starting_objects(std::slice::from_ref(&shared_object))
570            .build()
571            .await;
572        let certs = vec![
573            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 3),
574            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, false)], 5),
575            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 9),
576            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 11),
577        ];
578        let epoch_store = authority.epoch_store_for_testing();
579        let assignables = certs
580            .iter()
581            .map(Schedulable::Transaction)
582            .collect::<Vec<_>>();
583        let ConsensusSharedObjVerAssignment {
584            shared_input_next_versions,
585            assigned_versions,
586        } = SharedObjVerManager::assign_versions_from_consensus(
587            &epoch_store,
588            authority.get_object_cache_reader().as_ref(),
589            assignables.iter(),
590            &BTreeMap::new(),
591        )
592        .unwrap();
593        // Check that the shared object's next version is always initialized in the epoch store.
594        assert_eq!(
595            epoch_store
596                .get_next_object_version(&id, init_shared_version)
597                .unwrap(),
598            init_shared_version
599        );
600        // Check that the final version of the shared object is the lamport version of the last
601        // transaction.
602        assert_eq!(
603            *shared_input_next_versions
604                .get(&(id, init_shared_version))
605                .unwrap(),
606            SequenceNumber::from_u64(12)
607        );
608        // Check that the version assignment for each transaction is correct.
609        // For a transaction that uses the shared object with mutable=false, it won't update the version
610        // using lamport version, hence the next transaction will use the same version number.
611        // In the following case, certs[2] has the same assignment as certs[1] for this reason.
612        let expected_accumulator_version = SequenceNumber::from_u64(1);
613        assert_eq!(
614            assigned_versions.0,
615            vec![
616                (
617                    certs[0].key(),
618                    AssignedVersions::new(
619                        vec![((id, init_shared_version), init_shared_version)],
620                        Some(expected_accumulator_version)
621                    )
622                ),
623                (
624                    certs[1].key(),
625                    AssignedVersions::new(
626                        vec![((id, init_shared_version), SequenceNumber::from_u64(4))],
627                        Some(expected_accumulator_version)
628                    )
629                ),
630                (
631                    certs[2].key(),
632                    AssignedVersions::new(
633                        vec![((id, init_shared_version), SequenceNumber::from_u64(4))],
634                        Some(expected_accumulator_version)
635                    )
636                ),
637                (
638                    certs[3].key(),
639                    AssignedVersions::new(
640                        vec![((id, init_shared_version), SequenceNumber::from_u64(10))],
641                        Some(expected_accumulator_version)
642                    )
643                ),
644            ]
645        );
646    }
647
648    #[tokio::test]
649    async fn test_assign_versions_from_consensus_with_randomness() {
650        let authority = TestAuthorityBuilder::new().build().await;
651        let epoch_store = authority.epoch_store_for_testing();
652        let randomness_obj_version = epoch_store
653            .epoch_start_config()
654            .randomness_obj_initial_shared_version()
655            .unwrap();
656        let certs = vec![
657            VerifiedExecutableTransaction::new_system(
658                VerifiedTransaction::new_randomness_state_update(
659                    epoch_store.epoch(),
660                    RandomnessRound::new(1),
661                    vec![],
662                    randomness_obj_version,
663                ),
664                epoch_store.epoch(),
665            ),
666            generate_shared_objs_tx_with_gas_version(
667                &[(
668                    SUI_RANDOMNESS_STATE_OBJECT_ID,
669                    randomness_obj_version,
670                    // This can only be false since it's not allowed to use randomness object with mutable=true.
671                    false,
672                )],
673                3,
674            ),
675            generate_shared_objs_tx_with_gas_version(
676                &[(
677                    SUI_RANDOMNESS_STATE_OBJECT_ID,
678                    randomness_obj_version,
679                    false,
680                )],
681                5,
682            ),
683        ];
684        let assignables = certs
685            .iter()
686            .map(Schedulable::Transaction)
687            .collect::<Vec<_>>();
688        let ConsensusSharedObjVerAssignment {
689            shared_input_next_versions,
690            assigned_versions,
691        } = SharedObjVerManager::assign_versions_from_consensus(
692            &epoch_store,
693            authority.get_object_cache_reader().as_ref(),
694            assignables.iter(),
695            &BTreeMap::new(),
696        )
697        .unwrap();
698        // Check that the randomness object's next version is initialized.
699        assert_eq!(
700            epoch_store
701                .get_next_object_version(&SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version)
702                .unwrap(),
703            randomness_obj_version
704        );
705        let next_randomness_obj_version = randomness_obj_version.next();
706        assert_eq!(
707            *shared_input_next_versions
708                .get(&(SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version))
709                .unwrap(),
710            // Randomness object's version is only incremented by 1 regardless of lamport version.
711            next_randomness_obj_version
712        );
713        let expected_accumulator_version = SequenceNumber::from_u64(1);
714        assert_eq!(
715            assigned_versions.0,
716            vec![
717                (
718                    certs[0].key(),
719                    AssignedVersions::new(
720                        vec![(
721                            (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
722                            randomness_obj_version
723                        )],
724                        Some(expected_accumulator_version)
725                    )
726                ),
727                (
728                    certs[1].key(),
729                    // It is critical that the randomness object version is updated before the assignment.
730                    AssignedVersions::new(
731                        vec![(
732                            (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
733                            next_randomness_obj_version
734                        )],
735                        Some(expected_accumulator_version)
736                    )
737                ),
738                (
739                    certs[2].key(),
740                    // It is critical that the randomness object version is updated before the assignment.
741                    AssignedVersions::new(
742                        vec![(
743                            (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
744                            next_randomness_obj_version
745                        )],
746                        Some(expected_accumulator_version)
747                    )
748                ),
749            ]
750        );
751    }
752
753    // Tests shared object version assignment for cancelled transaction.
754    #[tokio::test]
755    async fn test_assign_versions_from_consensus_with_cancellation() {
756        let shared_object_1 = Object::shared_for_testing();
757        let shared_object_2 = Object::shared_for_testing();
758        let id1 = shared_object_1.id();
759        let id2 = shared_object_2.id();
760        let init_shared_version_1 = shared_object_1.owner.start_version().unwrap();
761        let init_shared_version_2 = shared_object_2.owner.start_version().unwrap();
762        let authority = TestAuthorityBuilder::new()
763            .with_starting_objects(&[shared_object_1.clone(), shared_object_2.clone()])
764            .build()
765            .await;
766        let randomness_obj_version = authority
767            .epoch_store_for_testing()
768            .epoch_start_config()
769            .randomness_obj_initial_shared_version()
770            .unwrap();
771
772        // Generate 5 transactions for testing.
773        //   tx1: shared_object_1, shared_object_2, owned_object_version = 3
774        //   tx2: shared_object_1, shared_object_2, owned_object_version = 5
775        //   tx3: shared_object_1, owned_object_version = 1
776        //   tx4: shared_object_1, shared_object_2, owned_object_version = 9
777        //   tx5: shared_object_1, shared_object_2, owned_object_version = 11
778        //
779        // Later, we cancel transaction 2 and 4 due to congestion, and 5 due to DKG failure.
780        // Expected outcome:
781        //   tx1: both shared objects assign version 1, lamport version = 4
782        //   tx2: shared objects assign cancelled version, lamport version = 6 due to gas object version = 5
783        //   tx3: shared object 1 assign version 4, lamport version = 5
784        //   tx4: shared objects assign cancelled version, lamport version = 10 due to gas object version = 9
785        //   tx5: shared objects assign cancelled version, lamport version = 12 due to gas object version = 11
786        let certs = vec![
787            generate_shared_objs_tx_with_gas_version(
788                &[
789                    (id1, init_shared_version_1, true),
790                    (id2, init_shared_version_2, true),
791                ],
792                3,
793            ),
794            generate_shared_objs_tx_with_gas_version(
795                &[
796                    (id1, init_shared_version_1, true),
797                    (id2, init_shared_version_2, true),
798                ],
799                5,
800            ),
801            generate_shared_objs_tx_with_gas_version(&[(id1, init_shared_version_1, true)], 1),
802            generate_shared_objs_tx_with_gas_version(
803                &[
804                    (id1, init_shared_version_1, true),
805                    (id2, init_shared_version_2, true),
806                ],
807                9,
808            ),
809            generate_shared_objs_tx_with_gas_version(
810                &[
811                    (
812                        SUI_RANDOMNESS_STATE_OBJECT_ID,
813                        randomness_obj_version,
814                        false,
815                    ),
816                    (id2, init_shared_version_2, true),
817                ],
818                11,
819            ),
820        ];
821        let epoch_store = authority.epoch_store_for_testing();
822
823        // Cancel transactions 2 and 4 due to congestion.
824        let cancelled_txns: BTreeMap<TransactionDigest, CancelConsensusCertificateReason> = [
825            (
826                *certs[1].digest(),
827                CancelConsensusCertificateReason::CongestionOnObjects(vec![id1]),
828            ),
829            (
830                *certs[3].digest(),
831                CancelConsensusCertificateReason::CongestionOnObjects(vec![id2]),
832            ),
833            (
834                *certs[4].digest(),
835                CancelConsensusCertificateReason::DkgFailed,
836            ),
837        ]
838        .into_iter()
839        .collect();
840
841        let assignables = certs
842            .iter()
843            .map(Schedulable::Transaction)
844            .collect::<Vec<_>>();
845
846        // Run version assignment logic.
847        let ConsensusSharedObjVerAssignment {
848            mut shared_input_next_versions,
849            assigned_versions,
850        } = SharedObjVerManager::assign_versions_from_consensus(
851            &epoch_store,
852            authority.get_object_cache_reader().as_ref(),
853            assignables.iter(),
854            &cancelled_txns,
855        )
856        .unwrap();
857
858        // Check that the final version of the shared object is the lamport version of the last
859        // transaction.
860        shared_input_next_versions
861            .remove(&(SUI_ACCUMULATOR_ROOT_OBJECT_ID, SequenceNumber::from_u64(1)));
862        assert_eq!(
863            shared_input_next_versions,
864            HashMap::from([
865                ((id1, init_shared_version_1), SequenceNumber::from_u64(5)), // determined by tx3
866                ((id2, init_shared_version_2), SequenceNumber::from_u64(4)), // determined by tx1
867                (
868                    (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
869                    SequenceNumber::from_u64(1)
870                ), // not mutable
871            ])
872        );
873
874        // Check that the version assignment for each transaction is correct.
875        let expected_accumulator_version = SequenceNumber::from_u64(1);
876        assert_eq!(
877            assigned_versions.0,
878            vec![
879                (
880                    certs[0].key(),
881                    AssignedVersions::new(
882                        vec![
883                            ((id1, init_shared_version_1), init_shared_version_1),
884                            ((id2, init_shared_version_2), init_shared_version_2)
885                        ],
886                        Some(expected_accumulator_version)
887                    )
888                ),
889                (
890                    certs[1].key(),
891                    AssignedVersions::new(
892                        vec![
893                            ((id1, init_shared_version_1), SequenceNumber::CONGESTED),
894                            ((id2, init_shared_version_2), SequenceNumber::CANCELLED_READ),
895                        ],
896                        Some(expected_accumulator_version)
897                    )
898                ),
899                (
900                    certs[2].key(),
901                    AssignedVersions::new(
902                        vec![((id1, init_shared_version_1), SequenceNumber::from_u64(4))],
903                        Some(expected_accumulator_version)
904                    )
905                ),
906                (
907                    certs[3].key(),
908                    AssignedVersions::new(
909                        vec![
910                            ((id1, init_shared_version_1), SequenceNumber::CANCELLED_READ),
911                            ((id2, init_shared_version_2), SequenceNumber::CONGESTED)
912                        ],
913                        Some(expected_accumulator_version)
914                    )
915                ),
916                (
917                    certs[4].key(),
918                    AssignedVersions::new(
919                        vec![
920                            (
921                                (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
922                                SequenceNumber::RANDOMNESS_UNAVAILABLE
923                            ),
924                            ((id2, init_shared_version_2), SequenceNumber::CANCELLED_READ)
925                        ],
926                        Some(expected_accumulator_version)
927                    )
928                ),
929            ]
930        );
931    }
932
933    #[tokio::test]
934    async fn test_assign_versions_from_effects() {
935        let shared_object = Object::shared_for_testing();
936        let id = shared_object.id();
937        let init_shared_version = shared_object.owner.start_version().unwrap();
938        let authority = TestAuthorityBuilder::new()
939            .with_starting_objects(std::slice::from_ref(&shared_object))
940            .build()
941            .await;
942        let certs = vec![
943            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 3),
944            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, false)], 5),
945            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 9),
946            generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 11),
947        ];
948        let effects = vec![
949            TestEffectsBuilder::new(certs[0].data()).build(),
950            TestEffectsBuilder::new(certs[1].data())
951                .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(4))]))
952                .build(),
953            TestEffectsBuilder::new(certs[2].data())
954                .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(4))]))
955                .build(),
956            TestEffectsBuilder::new(certs[3].data())
957                .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(10))]))
958                .build(),
959        ];
960        let epoch_store = authority.epoch_store_for_testing();
961        let assigned_versions = SharedObjVerManager::assign_versions_from_effects(
962            certs
963                .iter()
964                .zip(effects.iter())
965                .map(|(cert, effect)| (cert, effect, None))
966                .collect::<Vec<_>>()
967                .as_slice(),
968            &epoch_store,
969            authority.get_object_cache_reader().as_ref(),
970        );
971        // Check that the shared object's next version is always initialized in the epoch store.
972        assert_eq!(
973            epoch_store
974                .get_next_object_version(&id, init_shared_version)
975                .unwrap(),
976            init_shared_version
977        );
978        assert_eq!(
979            assigned_versions.0,
980            vec![
981                (
982                    certs[0].key(),
983                    AssignedVersions::new(
984                        vec![((id, init_shared_version), init_shared_version)],
985                        None
986                    )
987                ),
988                (
989                    certs[1].key(),
990                    AssignedVersions::new(
991                        vec![((id, init_shared_version), SequenceNumber::from_u64(4))],
992                        None
993                    )
994                ),
995                (
996                    certs[2].key(),
997                    AssignedVersions::new(
998                        vec![((id, init_shared_version), SequenceNumber::from_u64(4))],
999                        None
1000                    )
1001                ),
1002                (
1003                    certs[3].key(),
1004                    AssignedVersions::new(
1005                        vec![((id, init_shared_version), SequenceNumber::from_u64(10))],
1006                        None
1007                    )
1008                ),
1009            ]
1010        );
1011    }
1012
1013    /// Generate a transaction that uses shared objects as specified in the parameters.
1014    /// Also uses a gas object with specified version.
1015    /// The version of the gas object is used to manipulate the lamport version of this transaction.
1016    fn generate_shared_objs_tx_with_gas_version(
1017        shared_objects: &[(ObjectID, SequenceNumber, bool)],
1018        gas_object_version: u64,
1019    ) -> VerifiedExecutableTransaction {
1020        let mut tx_builder = TestTransactionBuilder::new(
1021            SuiAddress::ZERO,
1022            (
1023                ObjectID::random(),
1024                SequenceNumber::from_u64(gas_object_version),
1025                ObjectDigest::random(),
1026            ),
1027            0,
1028        );
1029        let tx_data = {
1030            let builder = tx_builder.ptb_builder_mut();
1031            for (shared_object_id, shared_object_init_version, shared_object_mutable) in
1032                shared_objects
1033            {
1034                builder
1035                    .obj(ObjectArg::SharedObject {
1036                        id: *shared_object_id,
1037                        initial_shared_version: *shared_object_init_version,
1038                        mutability: if *shared_object_mutable {
1039                            SharedObjectMutability::Mutable
1040                        } else {
1041                            SharedObjectMutability::Immutable
1042                        },
1043                    })
1044                    .unwrap();
1045            }
1046            tx_builder.build()
1047        };
1048        let tx = SenderSignedData::new(tx_data, vec![]);
1049        VerifiedExecutableTransaction::new_unchecked(ExecutableTransaction::new_from_data_and_sig(
1050            tx,
1051            CertificateProof::new_system(0),
1052        ))
1053    }
1054
1055    struct WithdrawTestContext {
1056        authority: Arc<AuthorityState>,
1057        assignables: Vec<Schedulable<VerifiedExecutableTransaction>>,
1058        shared_objects: Vec<Object>,
1059    }
1060
1061    impl WithdrawTestContext {
1062        pub async fn new() -> Self {
1063            // Create a shared object for testing
1064            let shared_objects = vec![Object::shared_for_testing()];
1065            let mut config = ProtocolConfig::get_for_max_version_UNSAFE();
1066            config.enable_accumulators_for_testing();
1067            let authority = TestAuthorityBuilder::new()
1068                .with_starting_objects(&shared_objects)
1069                .with_protocol_config(config)
1070                .build()
1071                .await;
1072            Self {
1073                authority,
1074                assignables: vec![],
1075                shared_objects,
1076            }
1077        }
1078
1079        pub fn add_withdraw_transaction(&mut self) -> TransactionKey {
1080            // Generate random sender and gas object for each transaction
1081            let (sender, keypair) = get_account_key_pair();
1082            let gas_object = Object::with_owner_for_testing(sender);
1083            let gas_object_ref = gas_object.compute_object_reference();
1084            // Generate a unique gas price to make the transaction unique.
1085            let gas_price = (self.assignables.len() + 1) as u64;
1086            let mut tx_builder = TestTransactionBuilder::new(sender, gas_object_ref, gas_price);
1087            let tx_data = {
1088                let ptb_builder = tx_builder.ptb_builder_mut();
1089                ptb_builder
1090                    .funds_withdrawal(FundsWithdrawalArg::balance_from_sender(
1091                        200,
1092                        GAS::type_tag(),
1093                    ))
1094                    .unwrap();
1095                tx_builder.build()
1096            };
1097            let cert = VerifiedExecutableTransaction::new_for_testing(tx_data, &keypair);
1098            let key = cert.key();
1099            self.assignables.push(Schedulable::Transaction(cert));
1100            key
1101        }
1102
1103        pub fn add_settlement_transaction(&mut self) -> TransactionKey {
1104            let height = (self.assignables.len() + 1) as u64;
1105            let settlement = Schedulable::AccumulatorSettlement(0, height);
1106            let key = settlement.key();
1107            self.assignables.push(settlement);
1108            key
1109        }
1110
1111        pub fn add_withdraw_with_shared_object_transaction(&mut self) -> TransactionKey {
1112            // Generate random sender and gas object for each transaction
1113            let (sender, keypair) = get_account_key_pair();
1114            let gas_object = Object::with_owner_for_testing(sender);
1115            let gas_object_ref = gas_object.compute_object_reference();
1116            // Generate a unique gas price to make the transaction unique.
1117            let gas_price = (self.assignables.len() + 1) as u64;
1118            let mut tx_builder = TestTransactionBuilder::new(sender, gas_object_ref, gas_price);
1119            let tx_data = {
1120                let ptb_builder = tx_builder.ptb_builder_mut();
1121                // Add shared object to the transaction
1122                if let Some(shared_obj) = self.shared_objects.first() {
1123                    let id = shared_obj.id();
1124                    let init_version = shared_obj.owner.start_version().unwrap();
1125                    ptb_builder
1126                        .obj(ObjectArg::SharedObject {
1127                            id,
1128                            initial_shared_version: init_version,
1129                            mutability: SharedObjectMutability::Mutable,
1130                        })
1131                        .unwrap();
1132                }
1133                // Add balance withdraw
1134                ptb_builder
1135                    .funds_withdrawal(FundsWithdrawalArg::balance_from_sender(
1136                        200,
1137                        GAS::type_tag(),
1138                    ))
1139                    .unwrap();
1140                tx_builder.build()
1141            };
1142            let cert = VerifiedExecutableTransaction::new_for_testing(tx_data, &keypair);
1143            let key = cert.key();
1144            self.assignables.push(Schedulable::Transaction(cert));
1145            key
1146        }
1147
1148        pub fn assign_versions_from_consensus(&self) -> ConsensusSharedObjVerAssignment {
1149            let epoch_store = self.authority.epoch_store_for_testing();
1150            SharedObjVerManager::assign_versions_from_consensus(
1151                &epoch_store,
1152                self.authority.get_object_cache_reader().as_ref(),
1153                self.assignables.iter(),
1154                &BTreeMap::new(),
1155            )
1156            .unwrap()
1157        }
1158    }
1159
1160    #[tokio::test]
1161    async fn test_assign_versions_from_consensus_with_withdraws_simple() {
1162        // Note that we don't need a shared object to trigger withdraw version assignment.
1163        // In fact it is important that this works without a shared object.
1164        let mut ctx = WithdrawTestContext::new().await;
1165
1166        let acc_version = ctx
1167            .authority
1168            .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1169            .await
1170            .unwrap()
1171            .version();
1172
1173        let withdraw_key = ctx.add_withdraw_transaction();
1174        let settlement_key = ctx.add_settlement_transaction();
1175
1176        let assigned_versions = ctx.assign_versions_from_consensus();
1177        assert_eq!(
1178            assigned_versions,
1179            ConsensusSharedObjVerAssignment {
1180                assigned_versions: AssignedTxAndVersions::new(vec![
1181                    (
1182                        withdraw_key,
1183                        AssignedVersions {
1184                            shared_object_versions: vec![],
1185                            accumulator_version: Some(acc_version),
1186                        }
1187                    ),
1188                    (
1189                        settlement_key,
1190                        AssignedVersions {
1191                            shared_object_versions: vec![(
1192                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1193                                acc_version
1194                            )],
1195                            accumulator_version: Some(acc_version),
1196                        }
1197                    ),
1198                ]),
1199                shared_input_next_versions: HashMap::from([(
1200                    (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1201                    acc_version.next()
1202                )]),
1203            }
1204        );
1205    }
1206
1207    #[tokio::test]
1208    async fn test_assign_versions_from_consensus_with_multiple_withdraws_and_settlements() {
1209        // Test with multiple withdrawals and multiple settlements, with settlement as the last transaction
1210        let mut ctx = WithdrawTestContext::new().await;
1211
1212        let acc_version = ctx
1213            .authority
1214            .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1215            .await
1216            .unwrap()
1217            .version();
1218
1219        // First withdrawal and settlement
1220        let withdraw_key1 = ctx.add_withdraw_transaction();
1221        let settlement_key1 = ctx.add_settlement_transaction();
1222
1223        // Second withdrawal and settlement
1224        let withdraw_key2 = ctx.add_withdraw_transaction();
1225        let settlement_key2 = ctx.add_settlement_transaction();
1226
1227        // Third withdrawal and final settlement
1228        let withdraw_key3 = ctx.add_withdraw_transaction();
1229        let settlement_key3 = ctx.add_settlement_transaction();
1230
1231        let assigned_versions = ctx.assign_versions_from_consensus();
1232        assert_eq!(
1233            assigned_versions,
1234            ConsensusSharedObjVerAssignment {
1235                assigned_versions: AssignedTxAndVersions::new(vec![
1236                    (
1237                        withdraw_key1,
1238                        AssignedVersions {
1239                            shared_object_versions: vec![],
1240                            accumulator_version: Some(acc_version),
1241                        }
1242                    ),
1243                    (
1244                        settlement_key1,
1245                        AssignedVersions {
1246                            shared_object_versions: vec![(
1247                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1248                                acc_version
1249                            )],
1250                            accumulator_version: Some(acc_version),
1251                        }
1252                    ),
1253                    (
1254                        withdraw_key2,
1255                        AssignedVersions {
1256                            shared_object_versions: vec![],
1257                            accumulator_version: Some(acc_version.next()),
1258                        }
1259                    ),
1260                    (
1261                        settlement_key2,
1262                        AssignedVersions {
1263                            shared_object_versions: vec![(
1264                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1265                                acc_version.next()
1266                            )],
1267                            accumulator_version: Some(acc_version.next()),
1268                        }
1269                    ),
1270                    (
1271                        withdraw_key3,
1272                        AssignedVersions {
1273                            shared_object_versions: vec![],
1274                            accumulator_version: Some(acc_version.next().next()),
1275                        }
1276                    ),
1277                    (
1278                        settlement_key3,
1279                        AssignedVersions {
1280                            shared_object_versions: vec![(
1281                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1282                                acc_version.next().next()
1283                            )],
1284                            accumulator_version: Some(acc_version.next().next()),
1285                        }
1286                    ),
1287                ]),
1288                shared_input_next_versions: HashMap::from([(
1289                    (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1290                    acc_version.next().next().next()
1291                )]),
1292            }
1293        );
1294    }
1295
1296    #[tokio::test]
1297    async fn test_assign_versions_from_consensus_with_withdraw_and_shared_object() {
1298        // Test that a transaction can have both a withdrawal and use a shared object
1299        let mut ctx = WithdrawTestContext::new().await;
1300
1301        // Get the shared object info from the context
1302        let shared_obj_id = ctx.shared_objects[0].id();
1303        let shared_obj_version = ctx.shared_objects[0].owner.start_version().unwrap();
1304
1305        let acc_version = ctx
1306            .authority
1307            .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1308            .await
1309            .unwrap()
1310            .version();
1311
1312        let withdraw_with_shared_key = ctx.add_withdraw_with_shared_object_transaction();
1313        let settlement_key = ctx.add_settlement_transaction();
1314
1315        let assigned_versions = ctx.assign_versions_from_consensus();
1316        assert_eq!(
1317            assigned_versions,
1318            ConsensusSharedObjVerAssignment {
1319                assigned_versions: AssignedTxAndVersions::new(vec![
1320                    (
1321                        withdraw_with_shared_key,
1322                        AssignedVersions {
1323                            shared_object_versions: vec![(
1324                                (shared_obj_id, shared_obj_version),
1325                                shared_obj_version
1326                            )],
1327                            accumulator_version: Some(acc_version),
1328                        }
1329                    ),
1330                    (
1331                        settlement_key,
1332                        AssignedVersions {
1333                            shared_object_versions: vec![(
1334                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1335                                acc_version
1336                            )],
1337                            accumulator_version: Some(acc_version),
1338                        }
1339                    ),
1340                ]),
1341                shared_input_next_versions: HashMap::from([
1342                    (
1343                        (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1344                        acc_version.next()
1345                    ),
1346                    (
1347                        (shared_obj_id, shared_obj_version),
1348                        shared_obj_version.next()
1349                    ),
1350                ]),
1351            }
1352        );
1353    }
1354}