sui_core/authority/
shared_object_version_manager.rs

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