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