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
31use super::epoch_start_configuration::EpochStartConfigTrait;
32
33pub struct SharedObjVerManager {}
34
35/// Version assignments for a single transaction
36#[derive(Debug, Clone, Default, PartialEq, Eq)]
37pub struct AssignedVersions {
38    pub shared_object_versions: Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
39    /// Accumulator version number at the beginning of the consensus commit
40    /// that this transaction belongs to. It is used to determine the deterministic
41    /// balance state of the accounts involved in funds withdrawals.
42    /// None only if accumulator is not enabled at protocol level.
43    /// TODO: Make it required once accumulator is enabled.
44    pub accumulator_version: Option<SequenceNumber>,
45}
46
47impl AssignedVersions {
48    pub fn new(
49        shared_object_versions: Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
50        accumulator_version: Option<SequenceNumber>,
51    ) -> Self {
52        Self {
53            shared_object_versions,
54            accumulator_version,
55        }
56    }
57
58    pub fn iter(&self) -> impl Iterator<Item = &(ConsensusObjectSequenceKey, SequenceNumber)> {
59        self.shared_object_versions.iter()
60    }
61
62    pub fn as_slice(&self) -> &[(ConsensusObjectSequenceKey, SequenceNumber)] {
63        &self.shared_object_versions
64    }
65}
66
67#[derive(Default, Debug, PartialEq, Eq)]
68pub struct AssignedTxAndVersions(pub Vec<(TransactionKey, AssignedVersions)>);
69
70impl AssignedTxAndVersions {
71    pub fn new(assigned_versions: Vec<(TransactionKey, AssignedVersions)>) -> Self {
72        Self(assigned_versions)
73    }
74
75    pub fn into_map(self) -> HashMap<TransactionKey, AssignedVersions> {
76        self.0.into_iter().collect()
77    }
78}
79
80/// A wrapper around things that can be scheduled for execution by the assigning of
81/// shared object versions.
82#[derive(Clone)]
83pub enum Schedulable<T = VerifiedExecutableTransaction> {
84    Transaction(T),
85    RandomnessStateUpdate(EpochId, RandomnessRound),
86    AccumulatorSettlement(EpochId, u64 /* checkpoint height */),
87    ConsensusCommitPrologue(EpochId, u64 /* round */, u32 /* sub_dag_index */),
88}
89
90impl From<VerifiedExecutableTransaction> for Schedulable<VerifiedExecutableTransaction> {
91    fn from(tx: VerifiedExecutableTransaction) -> Self {
92        Schedulable::Transaction(tx)
93    }
94}
95
96impl From<Schedulable<VerifiedExecutableTransactionWithAliases>>
97    for Schedulable<VerifiedExecutableTransaction>
98{
99    fn from(schedulable: Schedulable<VerifiedExecutableTransactionWithAliases>) -> Self {
100        match schedulable {
101            Schedulable::Transaction(tx) => Schedulable::Transaction(tx.into_tx()),
102            Schedulable::RandomnessStateUpdate(epoch, round) => {
103                Schedulable::RandomnessStateUpdate(epoch, round)
104            }
105            Schedulable::AccumulatorSettlement(epoch, checkpoint_height) => {
106                Schedulable::AccumulatorSettlement(epoch, checkpoint_height)
107            }
108            Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index) => {
109                Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index)
110            }
111        }
112    }
113}
114
115// AsTx is like Deref, in that it allows us to use either refs or values in Schedulable.
116// Deref does not work because it conflicts with the impl of Deref for VerifiedExecutableTransaction.
117pub trait AsTx {
118    fn as_tx(&self) -> &VerifiedExecutableTransaction;
119}
120
121impl AsTx for VerifiedExecutableTransaction {
122    fn as_tx(&self) -> &VerifiedExecutableTransaction {
123        self
124    }
125}
126
127impl AsTx for &'_ VerifiedExecutableTransaction {
128    fn as_tx(&self) -> &VerifiedExecutableTransaction {
129        self
130    }
131}
132
133impl AsTx for VerifiedExecutableTransactionWithAliases {
134    fn as_tx(&self) -> &VerifiedExecutableTransaction {
135        self.tx()
136    }
137}
138
139impl AsTx for &'_ VerifiedExecutableTransactionWithAliases {
140    fn as_tx(&self) -> &VerifiedExecutableTransaction {
141        self.tx()
142    }
143}
144
145impl Schedulable<&'_ VerifiedExecutableTransaction> {
146    // Cannot use the blanket ToOwned trait impl because it just calls clone.
147    pub fn to_owned_schedulable(&self) -> Schedulable<VerifiedExecutableTransaction> {
148        match self {
149            Schedulable::Transaction(tx) => Schedulable::Transaction((*tx).clone()),
150            Schedulable::RandomnessStateUpdate(epoch, round) => {
151                Schedulable::RandomnessStateUpdate(*epoch, *round)
152            }
153            Schedulable::AccumulatorSettlement(epoch, checkpoint_height) => {
154                Schedulable::AccumulatorSettlement(*epoch, *checkpoint_height)
155            }
156            Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index) => {
157                Schedulable::ConsensusCommitPrologue(*epoch, *round, *sub_dag_index)
158            }
159        }
160    }
161}
162
163impl<T> Schedulable<T> {
164    pub fn as_tx(&self) -> Option<&VerifiedExecutableTransaction>
165    where
166        T: AsTx,
167    {
168        match self {
169            Schedulable::Transaction(tx) => Some(tx.as_tx()),
170            Schedulable::RandomnessStateUpdate(_, _) => None,
171            Schedulable::AccumulatorSettlement(_, _) => None,
172            Schedulable::ConsensusCommitPrologue(_, _, _) => None,
173        }
174    }
175
176    pub fn shared_input_objects(
177        &self,
178        epoch_store: &AuthorityPerEpochStore,
179    ) -> impl Iterator<Item = SharedInputObject> + '_
180    where
181        T: AsTx,
182    {
183        match self {
184            Schedulable::Transaction(tx) => Either::Left(tx.as_tx().shared_input_objects()),
185            Schedulable::RandomnessStateUpdate(_, _) => {
186                Either::Right(std::iter::once(SharedInputObject {
187                    id: SUI_RANDOMNESS_STATE_OBJECT_ID,
188                    initial_shared_version: epoch_store
189                        .epoch_start_config()
190                        .randomness_obj_initial_shared_version()
191                        .expect("randomness obj initial shared version should be set"),
192                    mutability: SharedObjectMutability::Mutable,
193                }))
194            }
195            Schedulable::AccumulatorSettlement(_, _) => {
196                Either::Right(std::iter::once(SharedInputObject {
197                    id: SUI_ACCUMULATOR_ROOT_OBJECT_ID,
198                    initial_shared_version: epoch_store
199                        .epoch_start_config()
200                        .accumulator_root_obj_initial_shared_version()
201                        .expect("accumulator root obj initial shared version should be set"),
202                    mutability: SharedObjectMutability::Mutable,
203                }))
204            }
205            Schedulable::ConsensusCommitPrologue(_, _, _) => {
206                Either::Right(std::iter::once(SharedInputObject {
207                    id: SUI_CLOCK_OBJECT_ID,
208                    initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
209                    mutability: SharedObjectMutability::Mutable,
210                }))
211            }
212        }
213    }
214
215    pub fn non_shared_input_object_keys(&self) -> Vec<ObjectKey>
216    where
217        T: AsTx,
218    {
219        match self {
220            Schedulable::Transaction(tx) => transaction_non_shared_input_object_keys(tx.as_tx())
221                .expect("Transaction input should have been verified"),
222            Schedulable::RandomnessStateUpdate(_, _) => vec![],
223            Schedulable::AccumulatorSettlement(_, _) => vec![],
224            Schedulable::ConsensusCommitPrologue(_, _, _) => vec![],
225        }
226    }
227
228    pub fn receiving_object_keys(&self) -> Vec<ObjectKey>
229    where
230        T: AsTx,
231    {
232        match self {
233            Schedulable::Transaction(tx) => transaction_receiving_object_keys(tx.as_tx()),
234            Schedulable::RandomnessStateUpdate(_, _) => vec![],
235            Schedulable::AccumulatorSettlement(_, _) => vec![],
236            Schedulable::ConsensusCommitPrologue(_, _, _) => vec![],
237        }
238    }
239
240    pub fn key(&self) -> TransactionKey
241    where
242        T: AsTx,
243    {
244        match self {
245            Schedulable::Transaction(tx) => tx.as_tx().key(),
246            Schedulable::RandomnessStateUpdate(epoch, round) => {
247                TransactionKey::RandomnessRound(*epoch, *round)
248            }
249            Schedulable::AccumulatorSettlement(epoch, checkpoint_height) => {
250                TransactionKey::AccumulatorSettlement(*epoch, *checkpoint_height)
251            }
252            Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index) => {
253                TransactionKey::ConsensusCommitPrologue(*epoch, *round, *sub_dag_index)
254            }
255        }
256    }
257}
258
259#[must_use]
260#[derive(Default, Eq, PartialEq, Debug)]
261pub struct ConsensusSharedObjVerAssignment {
262    pub shared_input_next_versions: HashMap<ConsensusObjectSequenceKey, SequenceNumber>,
263    pub assigned_versions: AssignedTxAndVersions,
264}
265
266impl SharedObjVerManager {
267    pub fn assign_versions_from_consensus<'a, T>(
268        epoch_store: &AuthorityPerEpochStore,
269        cache_reader: &dyn ObjectCacheRead,
270        assignables: impl Iterator<Item = &'a Schedulable<T>> + Clone,
271        cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
272    ) -> SuiResult<ConsensusSharedObjVerAssignment>
273    where
274        T: AsTx + 'a,
275    {
276        let mut shared_input_next_versions = get_or_init_versions(
277            assignables
278                .clone()
279                .flat_map(|a| a.shared_input_objects(epoch_store)),
280            epoch_store,
281            cache_reader,
282        )?;
283        let mut assigned_versions = Vec::new();
284        for assignable in assignables {
285            assert!(
286                !matches!(assignable, Schedulable::AccumulatorSettlement(_, _))
287                    || epoch_store.accumulators_enabled(),
288                "AccumulatorSettlement should not be scheduled when accumulators are disabled"
289            );
290
291            let cert_assigned_versions = Self::assign_versions_for_certificate(
292                epoch_store,
293                assignable,
294                &mut shared_input_next_versions,
295                cancelled_txns,
296            );
297            assigned_versions.push((assignable.key(), cert_assigned_versions));
298        }
299
300        Ok(ConsensusSharedObjVerAssignment {
301            shared_input_next_versions,
302            assigned_versions: AssignedTxAndVersions::new(assigned_versions),
303        })
304    }
305
306    pub fn assign_versions_from_effects(
307        certs_and_effects: &[(
308            &VerifiedExecutableTransaction,
309            &TransactionEffects,
310            // Accumulator version
311            Option<SequenceNumber>,
312        )],
313        epoch_store: &AuthorityPerEpochStore,
314        cache_reader: &dyn ObjectCacheRead,
315    ) -> AssignedTxAndVersions {
316        // We don't care about the results since we can use effects to assign versions.
317        // But we must call it to make sure whenever a consensus object is touched the first time
318        // during an epoch, either through consensus or through checkpoint executor,
319        // its next version must be initialized. This is because we initialize the next version
320        // of a consensus object in an epoch by reading the current version from the object store.
321        // This must be done before we mutate it the first time, otherwise we would be initializing
322        // it with the wrong version.
323        let _ = get_or_init_versions(
324            certs_and_effects.iter().flat_map(|(cert, _, _)| {
325                cert.transaction_data().shared_input_objects().into_iter()
326            }),
327            epoch_store,
328            cache_reader,
329        );
330        let mut assigned_versions = Vec::new();
331        for (cert, effects, accumulator_version) in certs_and_effects {
332            let initial_version_map: BTreeMap<_, _> = cert
333                .transaction_data()
334                .shared_input_objects()
335                .into_iter()
336                .map(|input| input.into_id_and_version())
337                .collect();
338            let cert_assigned_versions: Vec<_> = effects
339                .input_consensus_objects()
340                .into_iter()
341                .map(|iso| {
342                    let (id, version) = iso.id_and_version();
343                    let initial_version = initial_version_map
344                        .get(&id)
345                        .expect("transaction must have all inputs from effects");
346                    ((id, *initial_version), version)
347                })
348                .collect();
349            let tx_key = cert.key();
350            trace!(
351                ?tx_key,
352                ?cert_assigned_versions,
353                "assigned consensus object versions from effects"
354            );
355            assigned_versions.push((
356                tx_key,
357                AssignedVersions::new(cert_assigned_versions, *accumulator_version),
358            ));
359        }
360        AssignedTxAndVersions::new(assigned_versions)
361    }
362
363    pub fn assign_versions_for_certificate(
364        epoch_store: &AuthorityPerEpochStore,
365        assignable: &Schedulable<impl AsTx>,
366        shared_input_next_versions: &mut HashMap<ConsensusObjectSequenceKey, SequenceNumber>,
367        cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
368    ) -> AssignedVersions {
369        let shared_input_objects: Vec<_> = assignable.shared_input_objects(epoch_store).collect();
370
371        let accumulator_version = if epoch_store.accumulators_enabled() {
372            let accumulator_initial_version = epoch_store
373                .epoch_start_config()
374                .accumulator_root_obj_initial_shared_version()
375                .expect("accumulator root obj initial shared version should be set when accumulators are enabled");
376
377            let accumulator_version = *shared_input_next_versions
378                .get(&(SUI_ACCUMULATOR_ROOT_OBJECT_ID, accumulator_initial_version))
379                .expect("accumulator object must be in shared_input_next_versions when withdraws are enabled");
380
381            Some(accumulator_version)
382        } else {
383            None
384        };
385
386        if shared_input_objects.is_empty() {
387            // No shared object used by this transaction. No need to assign versions.
388            return AssignedVersions::new(vec![], accumulator_version);
389        }
390
391        let tx_key = assignable.key();
392
393        // Check if the transaction is cancelled due to congestion.
394        let cancellation_info = tx_key
395            .as_digest()
396            .and_then(|tx_digest| cancelled_txns.get(tx_digest));
397        let congested_objects_info: Option<HashSet<_>> =
398            if let Some(CancelConsensusCertificateReason::CongestionOnObjects(congested_objects)) =
399                &cancellation_info
400            {
401                Some(congested_objects.iter().cloned().collect())
402            } else {
403                None
404            };
405        let txn_cancelled = cancellation_info.is_some();
406
407        let mut input_object_keys = assignable.non_shared_input_object_keys();
408        let mut assigned_versions = Vec::with_capacity(shared_input_objects.len());
409        let mut is_exclusively_accessed_input = Vec::with_capacity(shared_input_objects.len());
410        // Record receiving object versions towards the shared version computation.
411        let receiving_object_keys = assignable.receiving_object_keys();
412        input_object_keys.extend(receiving_object_keys);
413
414        if txn_cancelled {
415            // For cancelled transaction due to congestion, assign special versions to all shared objects.
416            // Note that new lamport version does not depend on any shared objects.
417            for SharedInputObject {
418                id,
419                initial_shared_version,
420                ..
421            } in shared_input_objects.iter()
422            {
423                let assigned_version = match cancellation_info {
424                    Some(CancelConsensusCertificateReason::CongestionOnObjects(_)) => {
425                        if congested_objects_info
426                            .as_ref()
427                            .is_some_and(|info| info.contains(id))
428                        {
429                            SequenceNumber::CONGESTED
430                        } else {
431                            SequenceNumber::CANCELLED_READ
432                        }
433                    }
434                    Some(CancelConsensusCertificateReason::DkgFailed) => {
435                        if id == &SUI_RANDOMNESS_STATE_OBJECT_ID {
436                            SequenceNumber::RANDOMNESS_UNAVAILABLE
437                        } else {
438                            SequenceNumber::CANCELLED_READ
439                        }
440                    }
441                    None => unreachable!("cancelled transaction should have cancellation info"),
442                };
443                assigned_versions.push(((*id, *initial_shared_version), assigned_version));
444                is_exclusively_accessed_input.push(false);
445            }
446        } else {
447            for (
448                SharedInputObject {
449                    id,
450                    initial_shared_version,
451                    mutability,
452                },
453                assigned_version,
454            ) in shared_input_objects.iter().map(|obj| {
455                (
456                    obj,
457                    *shared_input_next_versions
458                        .get(&obj.id_and_version())
459                        .unwrap(),
460                )
461            }) {
462                assigned_versions.push(((*id, *initial_shared_version), assigned_version));
463                input_object_keys.push(ObjectKey(*id, assigned_version));
464                is_exclusively_accessed_input.push(mutability.is_exclusive());
465            }
466        }
467
468        let next_version =
469            SequenceNumber::lamport_increment(input_object_keys.iter().map(|obj| obj.1));
470        assert!(
471            next_version.is_valid(),
472            "Assigned version must be valid. Got {:?}",
473            next_version
474        );
475
476        if !txn_cancelled {
477            // Update the next version for the shared objects.
478            assigned_versions
479                .iter()
480                .zip_debug_eq(is_exclusively_accessed_input)
481                .filter_map(|((id, _), mutable)| {
482                    if mutable {
483                        Some((*id, next_version))
484                    } else {
485                        None
486                    }
487                })
488                .for_each(|(id, version)| {
489                    assert!(
490                        version.is_valid(),
491                        "Assigned version must be a valid version."
492                    );
493                    shared_input_next_versions
494                        .insert(id, version)
495                        .expect("Object must exist in shared_input_next_versions.");
496                });
497        }
498
499        trace!(
500            ?tx_key,
501            ?assigned_versions,
502            ?next_version,
503            ?txn_cancelled,
504            "locking shared objects"
505        );
506
507        AssignedVersions::new(assigned_versions, accumulator_version)
508    }
509}
510
511fn get_or_init_versions<'a>(
512    shared_input_objects: impl Iterator<Item = SharedInputObject> + 'a,
513    epoch_store: &AuthorityPerEpochStore,
514    cache_reader: &dyn ObjectCacheRead,
515) -> SuiResult<HashMap<ConsensusObjectSequenceKey, SequenceNumber>> {
516    let mut shared_input_objects: Vec<_> = shared_input_objects
517        .map(|so| so.into_id_and_version())
518        .collect();
519
520    if epoch_store.accumulators_enabled() {
521        shared_input_objects.push((
522            SUI_ACCUMULATOR_ROOT_OBJECT_ID,
523            epoch_store
524                .epoch_start_config()
525                .accumulator_root_obj_initial_shared_version()
526                .expect("accumulator root obj initial shared version should be set"),
527        ));
528    }
529
530    shared_input_objects.sort();
531    shared_input_objects.dedup();
532
533    epoch_store.get_or_init_next_object_versions(&shared_input_objects, cache_reader)
534}
535
536#[cfg(test)]
537mod tests {
538    use super::*;
539
540    use crate::authority::AuthorityState;
541    use crate::authority::epoch_start_configuration::EpochStartConfigTrait;
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            .await
1172            .unwrap()
1173            .version();
1174
1175        let withdraw_key = ctx.add_withdraw_transaction();
1176        let settlement_key = ctx.add_settlement_transaction();
1177
1178        let assigned_versions = ctx.assign_versions_from_consensus();
1179        assert_eq!(
1180            assigned_versions,
1181            ConsensusSharedObjVerAssignment {
1182                assigned_versions: AssignedTxAndVersions::new(vec![
1183                    (
1184                        withdraw_key,
1185                        AssignedVersions {
1186                            shared_object_versions: vec![],
1187                            accumulator_version: Some(acc_version),
1188                        }
1189                    ),
1190                    (
1191                        settlement_key,
1192                        AssignedVersions {
1193                            shared_object_versions: vec![(
1194                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1195                                acc_version
1196                            )],
1197                            accumulator_version: Some(acc_version),
1198                        }
1199                    ),
1200                ]),
1201                shared_input_next_versions: HashMap::from([(
1202                    (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1203                    acc_version.next()
1204                )]),
1205            }
1206        );
1207    }
1208
1209    #[tokio::test]
1210    async fn test_assign_versions_from_consensus_with_multiple_withdraws_and_settlements() {
1211        // Test with multiple withdrawals and multiple settlements, with settlement as the last transaction
1212        let mut ctx = WithdrawTestContext::new().await;
1213
1214        let acc_version = ctx
1215            .authority
1216            .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1217            .await
1218            .unwrap()
1219            .version();
1220
1221        // First withdrawal and settlement
1222        let withdraw_key1 = ctx.add_withdraw_transaction();
1223        let settlement_key1 = ctx.add_settlement_transaction();
1224
1225        // Second withdrawal and settlement
1226        let withdraw_key2 = ctx.add_withdraw_transaction();
1227        let settlement_key2 = ctx.add_settlement_transaction();
1228
1229        // Third withdrawal and final settlement
1230        let withdraw_key3 = ctx.add_withdraw_transaction();
1231        let settlement_key3 = ctx.add_settlement_transaction();
1232
1233        let assigned_versions = ctx.assign_versions_from_consensus();
1234        assert_eq!(
1235            assigned_versions,
1236            ConsensusSharedObjVerAssignment {
1237                assigned_versions: AssignedTxAndVersions::new(vec![
1238                    (
1239                        withdraw_key1,
1240                        AssignedVersions {
1241                            shared_object_versions: vec![],
1242                            accumulator_version: Some(acc_version),
1243                        }
1244                    ),
1245                    (
1246                        settlement_key1,
1247                        AssignedVersions {
1248                            shared_object_versions: vec![(
1249                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1250                                acc_version
1251                            )],
1252                            accumulator_version: Some(acc_version),
1253                        }
1254                    ),
1255                    (
1256                        withdraw_key2,
1257                        AssignedVersions {
1258                            shared_object_versions: vec![],
1259                            accumulator_version: Some(acc_version.next()),
1260                        }
1261                    ),
1262                    (
1263                        settlement_key2,
1264                        AssignedVersions {
1265                            shared_object_versions: vec![(
1266                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1267                                acc_version.next()
1268                            )],
1269                            accumulator_version: Some(acc_version.next()),
1270                        }
1271                    ),
1272                    (
1273                        withdraw_key3,
1274                        AssignedVersions {
1275                            shared_object_versions: vec![],
1276                            accumulator_version: Some(acc_version.next().next()),
1277                        }
1278                    ),
1279                    (
1280                        settlement_key3,
1281                        AssignedVersions {
1282                            shared_object_versions: vec![(
1283                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1284                                acc_version.next().next()
1285                            )],
1286                            accumulator_version: Some(acc_version.next().next()),
1287                        }
1288                    ),
1289                ]),
1290                shared_input_next_versions: HashMap::from([(
1291                    (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1292                    acc_version.next().next().next()
1293                )]),
1294            }
1295        );
1296    }
1297
1298    #[tokio::test]
1299    async fn test_assign_versions_from_consensus_with_withdraw_and_shared_object() {
1300        // Test that a transaction can have both a withdrawal and use a shared object
1301        let mut ctx = WithdrawTestContext::new().await;
1302
1303        // Get the shared object info from the context
1304        let shared_obj_id = ctx.shared_objects[0].id();
1305        let shared_obj_version = ctx.shared_objects[0].owner.start_version().unwrap();
1306
1307        let acc_version = ctx
1308            .authority
1309            .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1310            .await
1311            .unwrap()
1312            .version();
1313
1314        let withdraw_with_shared_key = ctx.add_withdraw_with_shared_object_transaction();
1315        let settlement_key = ctx.add_settlement_transaction();
1316
1317        let assigned_versions = ctx.assign_versions_from_consensus();
1318        assert_eq!(
1319            assigned_versions,
1320            ConsensusSharedObjVerAssignment {
1321                assigned_versions: AssignedTxAndVersions::new(vec![
1322                    (
1323                        withdraw_with_shared_key,
1324                        AssignedVersions {
1325                            shared_object_versions: vec![(
1326                                (shared_obj_id, shared_obj_version),
1327                                shared_obj_version
1328                            )],
1329                            accumulator_version: Some(acc_version),
1330                        }
1331                    ),
1332                    (
1333                        settlement_key,
1334                        AssignedVersions {
1335                            shared_object_versions: vec![(
1336                                (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1337                                acc_version
1338                            )],
1339                            accumulator_version: Some(acc_version),
1340                        }
1341                    ),
1342                ]),
1343                shared_input_next_versions: HashMap::from([
1344                    (
1345                        (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1346                        acc_version.next()
1347                    ),
1348                    (
1349                        (shared_obj_id, shared_obj_version),
1350                        shared_obj_version.next()
1351                    ),
1352                ]),
1353            }
1354        );
1355    }
1356}