1use 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#[derive(Debug, Clone, Default, PartialEq, Eq)]
37pub struct AssignedVersions {
38 pub shared_object_versions: Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
39 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#[derive(Clone)]
83pub enum Schedulable<T = VerifiedExecutableTransaction> {
84 Transaction(T),
85 RandomnessStateUpdate(EpochId, RandomnessRound),
86 AccumulatorSettlement(EpochId, u64 ),
87 ConsensusCommitPrologue(EpochId, u64 , u32 ),
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
115pub 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 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 Option<SequenceNumber>,
312 )],
313 epoch_store: &AuthorityPerEpochStore,
314 cache_reader: &dyn ObjectCacheRead,
315 ) -> AssignedTxAndVersions {
316 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 return AssignedVersions::new(vec![], accumulator_version);
389 }
390
391 let tx_key = assignable.key();
392
393 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 let receiving_object_keys = assignable.receiving_object_keys();
412 input_object_keys.extend(receiving_object_keys);
413
414 if txn_cancelled {
415 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 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 assert_eq!(
597 epoch_store
598 .get_next_object_version(&id, init_shared_version)
599 .unwrap(),
600 init_shared_version
601 );
602 assert_eq!(
605 *shared_input_next_versions
606 .get(&(id, init_shared_version))
607 .unwrap(),
608 SequenceNumber::from_u64(12)
609 );
610 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 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 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 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 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 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 #[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 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 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 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 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)), ((id2, init_shared_version_2), SequenceNumber::from_u64(4)), (
870 (SUI_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),
871 SequenceNumber::from_u64(1)
872 ), ])
874 );
875
876 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 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 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 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 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 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 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 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 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 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 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 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 let withdraw_key1 = ctx.add_withdraw_transaction();
1223 let settlement_key1 = ctx.add_settlement_transaction();
1224
1225 let withdraw_key2 = ctx.add_withdraw_transaction();
1227 let settlement_key2 = ctx.add_settlement_transaction();
1228
1229 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 let mut ctx = WithdrawTestContext::new().await;
1302
1303 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}