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