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
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>,
46}
47
48impl AssignedVersions {
49 pub fn new(
50 shared_object_versions: Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
51 accumulator_version: Option<SequenceNumber>,
52 ) -> Self {
53 Self {
54 shared_object_versions,
55 accumulator_version,
56 }
57 }
58
59 pub fn iter(&self) -> impl Iterator<Item = &(ConsensusObjectSequenceKey, SequenceNumber)> {
60 self.shared_object_versions.iter()
61 }
62
63 pub fn as_slice(&self) -> &[(ConsensusObjectSequenceKey, SequenceNumber)] {
64 &self.shared_object_versions
65 }
66}
67
68#[derive(Default, Debug, PartialEq, Eq)]
69pub struct AssignedTxAndVersions(pub Vec<(TransactionKey, AssignedVersions)>);
70
71impl AssignedTxAndVersions {
72 pub fn new(assigned_versions: Vec<(TransactionKey, AssignedVersions)>) -> Self {
73 Self(assigned_versions)
74 }
75
76 pub fn into_map(self) -> HashMap<TransactionKey, AssignedVersions> {
77 self.0.into_iter().collect()
78 }
79}
80
81#[derive(Clone)]
84pub enum Schedulable<T = VerifiedExecutableTransaction> {
85 Transaction(T),
86 RandomnessStateUpdate(EpochId, RandomnessRound),
87 AccumulatorSettlement(EpochId, u64 ),
88 ConsensusCommitPrologue(EpochId, u64 , u32 ),
89}
90
91impl From<VerifiedExecutableTransaction> for Schedulable<VerifiedExecutableTransaction> {
92 fn from(tx: VerifiedExecutableTransaction) -> Self {
93 Schedulable::Transaction(tx)
94 }
95}
96
97impl From<Schedulable<VerifiedExecutableTransactionWithAliases>>
98 for Schedulable<VerifiedExecutableTransaction>
99{
100 fn from(schedulable: Schedulable<VerifiedExecutableTransactionWithAliases>) -> Self {
101 match schedulable {
102 Schedulable::Transaction(tx) => Schedulable::Transaction(tx.into_tx()),
103 Schedulable::RandomnessStateUpdate(epoch, round) => {
104 Schedulable::RandomnessStateUpdate(epoch, round)
105 }
106 Schedulable::AccumulatorSettlement(epoch, checkpoint_height) => {
107 Schedulable::AccumulatorSettlement(epoch, checkpoint_height)
108 }
109 Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index) => {
110 Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index)
111 }
112 }
113 }
114}
115
116pub trait AsTx {
119 fn as_tx(&self) -> &VerifiedExecutableTransaction;
120}
121
122impl AsTx for VerifiedExecutableTransaction {
123 fn as_tx(&self) -> &VerifiedExecutableTransaction {
124 self
125 }
126}
127
128impl AsTx for &'_ VerifiedExecutableTransaction {
129 fn as_tx(&self) -> &VerifiedExecutableTransaction {
130 self
131 }
132}
133
134impl AsTx for VerifiedExecutableTransactionWithAliases {
135 fn as_tx(&self) -> &VerifiedExecutableTransaction {
136 self.tx()
137 }
138}
139
140impl AsTx for &'_ VerifiedExecutableTransactionWithAliases {
141 fn as_tx(&self) -> &VerifiedExecutableTransaction {
142 self.tx()
143 }
144}
145
146impl Schedulable<&'_ VerifiedExecutableTransaction> {
147 pub fn to_owned_schedulable(&self) -> Schedulable<VerifiedExecutableTransaction> {
149 match self {
150 Schedulable::Transaction(tx) => Schedulable::Transaction((*tx).clone()),
151 Schedulable::RandomnessStateUpdate(epoch, round) => {
152 Schedulable::RandomnessStateUpdate(*epoch, *round)
153 }
154 Schedulable::AccumulatorSettlement(epoch, checkpoint_height) => {
155 Schedulable::AccumulatorSettlement(*epoch, *checkpoint_height)
156 }
157 Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index) => {
158 Schedulable::ConsensusCommitPrologue(*epoch, *round, *sub_dag_index)
159 }
160 }
161 }
162}
163
164impl<T> Schedulable<T> {
165 pub fn as_tx(&self) -> Option<&VerifiedExecutableTransaction>
166 where
167 T: AsTx,
168 {
169 match self {
170 Schedulable::Transaction(tx) => Some(tx.as_tx()),
171 Schedulable::RandomnessStateUpdate(_, _) => None,
172 Schedulable::AccumulatorSettlement(_, _) => None,
173 Schedulable::ConsensusCommitPrologue(_, _, _) => None,
174 }
175 }
176
177 pub fn shared_input_objects(
178 &self,
179 epoch_store: &AuthorityPerEpochStore,
180 ) -> impl Iterator<Item = SharedInputObject> + '_
181 where
182 T: AsTx,
183 {
184 match self {
185 Schedulable::Transaction(tx) => Either::Left(tx.as_tx().shared_input_objects()),
186 Schedulable::RandomnessStateUpdate(_, _) => {
187 Either::Right(std::iter::once(SharedInputObject {
188 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
189 initial_shared_version: epoch_store
190 .epoch_start_config()
191 .randomness_obj_initial_shared_version()
192 .expect("randomness obj initial shared version should be set"),
193 mutability: SharedObjectMutability::Mutable,
194 }))
195 }
196 Schedulable::AccumulatorSettlement(_, _) => {
197 Either::Right(std::iter::once(SharedInputObject {
198 id: SUI_ACCUMULATOR_ROOT_OBJECT_ID,
199 initial_shared_version: epoch_store
200 .epoch_start_config()
201 .accumulator_root_obj_initial_shared_version()
202 .expect("accumulator root obj initial shared version should be set"),
203 mutability: SharedObjectMutability::Mutable,
204 }))
205 }
206 Schedulable::ConsensusCommitPrologue(_, _, _) => {
207 Either::Right(std::iter::once(SharedInputObject {
208 id: SUI_CLOCK_OBJECT_ID,
209 initial_shared_version: SUI_CLOCK_OBJECT_SHARED_VERSION,
210 mutability: SharedObjectMutability::Mutable,
211 }))
212 }
213 }
214 }
215
216 pub fn non_shared_input_object_keys(&self) -> Vec<ObjectKey>
217 where
218 T: AsTx,
219 {
220 match self {
221 Schedulable::Transaction(tx) => transaction_non_shared_input_object_keys(tx.as_tx())
222 .expect("Transaction input should have been verified"),
223 Schedulable::RandomnessStateUpdate(_, _) => vec![],
224 Schedulable::AccumulatorSettlement(_, _) => vec![],
225 Schedulable::ConsensusCommitPrologue(_, _, _) => vec![],
226 }
227 }
228
229 pub fn receiving_object_keys(&self) -> Vec<ObjectKey>
230 where
231 T: AsTx,
232 {
233 match self {
234 Schedulable::Transaction(tx) => transaction_receiving_object_keys(tx.as_tx()),
235 Schedulable::RandomnessStateUpdate(_, _) => vec![],
236 Schedulable::AccumulatorSettlement(_, _) => vec![],
237 Schedulable::ConsensusCommitPrologue(_, _, _) => vec![],
238 }
239 }
240
241 pub fn key(&self) -> TransactionKey
242 where
243 T: AsTx,
244 {
245 match self {
246 Schedulable::Transaction(tx) => tx.as_tx().key(),
247 Schedulable::RandomnessStateUpdate(epoch, round) => {
248 TransactionKey::RandomnessRound(*epoch, *round)
249 }
250 Schedulable::AccumulatorSettlement(epoch, checkpoint_height) => {
251 TransactionKey::AccumulatorSettlement(*epoch, *checkpoint_height)
252 }
253 Schedulable::ConsensusCommitPrologue(epoch, round, sub_dag_index) => {
254 TransactionKey::ConsensusCommitPrologue(*epoch, *round, *sub_dag_index)
255 }
256 }
257 }
258}
259
260#[must_use]
261#[derive(Default, Eq, PartialEq, Debug)]
262pub struct ConsensusSharedObjVerAssignment {
263 pub shared_input_next_versions: HashMap<ConsensusObjectSequenceKey, SequenceNumber>,
264 pub assigned_versions: AssignedTxAndVersions,
265}
266
267impl SharedObjVerManager {
268 pub fn assign_versions_from_consensus<'a, T>(
269 epoch_store: &AuthorityPerEpochStore,
270 cache_reader: &dyn ObjectCacheRead,
271 assignables: impl Iterator<Item = &'a Schedulable<T>> + Clone,
272 cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
273 ) -> SuiResult<ConsensusSharedObjVerAssignment>
274 where
275 T: AsTx + 'a,
276 {
277 let mut shared_input_next_versions = get_or_init_versions(
278 assignables
279 .clone()
280 .flat_map(|a| a.shared_input_objects(epoch_store)),
281 epoch_store,
282 cache_reader,
283 )?;
284 let mut assigned_versions = Vec::new();
285 for assignable in assignables {
286 assert!(
287 !matches!(assignable, Schedulable::AccumulatorSettlement(_, _))
288 || epoch_store.accumulators_enabled(),
289 "AccumulatorSettlement should not be scheduled when accumulators are disabled"
290 );
291
292 let cert_assigned_versions = Self::assign_versions_for_certificate(
293 epoch_store,
294 assignable,
295 &mut shared_input_next_versions,
296 cancelled_txns,
297 );
298 assigned_versions.push((assignable.key(), cert_assigned_versions));
299 }
300
301 Ok(ConsensusSharedObjVerAssignment {
302 shared_input_next_versions,
303 assigned_versions: AssignedTxAndVersions::new(assigned_versions),
304 })
305 }
306
307 pub fn assign_versions_from_effects(
308 certs_and_effects: &[(
309 &VerifiedExecutableTransaction,
310 &TransactionEffects,
311 Option<SequenceNumber>,
313 )],
314 epoch_store: &AuthorityPerEpochStore,
315 cache_reader: &dyn ObjectCacheRead,
316 ) -> AssignedTxAndVersions {
317 let _ = get_or_init_versions(
325 certs_and_effects.iter().flat_map(|(cert, _, _)| {
326 cert.transaction_data().shared_input_objects().into_iter()
327 }),
328 epoch_store,
329 cache_reader,
330 );
331 let mut assigned_versions = Vec::new();
332 for (cert, effects, accumulator_version) in certs_and_effects {
333 let initial_version_map: BTreeMap<_, _> = cert
334 .transaction_data()
335 .shared_input_objects()
336 .into_iter()
337 .map(|input| input.into_id_and_version())
338 .collect();
339 let cert_assigned_versions: Vec<_> = effects
340 .input_consensus_objects()
341 .into_iter()
342 .map(|iso| {
343 let (id, version) = iso.id_and_version();
344 let initial_version = initial_version_map
345 .get(&id)
346 .expect("transaction must have all inputs from effects");
347 ((id, *initial_version), version)
348 })
349 .collect();
350 let tx_key = cert.key();
351 trace!(
352 ?tx_key,
353 ?cert_assigned_versions,
354 "assigned consensus object versions from effects"
355 );
356 assigned_versions.push((
357 tx_key,
358 AssignedVersions::new(cert_assigned_versions, *accumulator_version),
359 ));
360 }
361 AssignedTxAndVersions::new(assigned_versions)
362 }
363
364 pub fn assign_versions_for_certificate(
365 epoch_store: &AuthorityPerEpochStore,
366 assignable: &Schedulable<impl AsTx>,
367 shared_input_next_versions: &mut HashMap<ConsensusObjectSequenceKey, SequenceNumber>,
368 cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
369 ) -> AssignedVersions {
370 let shared_input_objects: Vec<_> = assignable.shared_input_objects(epoch_store).collect();
371
372 let accumulator_version = if epoch_store.accumulators_enabled() {
373 let accumulator_initial_version = epoch_store
374 .epoch_start_config()
375 .accumulator_root_obj_initial_shared_version()
376 .expect("accumulator root obj initial shared version should be set when accumulators are enabled");
377
378 let accumulator_version = *shared_input_next_versions
379 .get(&(SUI_ACCUMULATOR_ROOT_OBJECT_ID, accumulator_initial_version))
380 .expect("accumulator object must be in shared_input_next_versions when withdraws are enabled");
381
382 Some(accumulator_version)
383 } else {
384 None
385 };
386
387 if shared_input_objects.is_empty() {
388 return AssignedVersions::new(vec![], accumulator_version);
390 }
391
392 let tx_key = assignable.key();
393
394 let cancellation_info = tx_key
396 .as_digest()
397 .and_then(|tx_digest| cancelled_txns.get(tx_digest));
398 let congested_objects_info: Option<HashSet<_>> =
399 if let Some(CancelConsensusCertificateReason::CongestionOnObjects(congested_objects)) =
400 &cancellation_info
401 {
402 Some(congested_objects.iter().cloned().collect())
403 } else {
404 None
405 };
406 let txn_cancelled = cancellation_info.is_some();
407
408 let mut input_object_keys = assignable.non_shared_input_object_keys();
409 let mut assigned_versions = Vec::with_capacity(shared_input_objects.len());
410 let mut is_exclusively_accessed_input = Vec::with_capacity(shared_input_objects.len());
411 let receiving_object_keys = assignable.receiving_object_keys();
413 input_object_keys.extend(receiving_object_keys);
414
415 if txn_cancelled {
416 for SharedInputObject {
419 id,
420 initial_shared_version,
421 ..
422 } in shared_input_objects.iter()
423 {
424 let assigned_version = match cancellation_info {
425 Some(CancelConsensusCertificateReason::CongestionOnObjects(_)) => {
426 if congested_objects_info
427 .as_ref()
428 .is_some_and(|info| info.contains(id))
429 {
430 SequenceNumber::CONGESTED
431 } else {
432 SequenceNumber::CANCELLED_READ
433 }
434 }
435 Some(CancelConsensusCertificateReason::DkgFailed) => {
436 if id == &SUI_RANDOMNESS_STATE_OBJECT_ID {
437 SequenceNumber::RANDOMNESS_UNAVAILABLE
438 } else {
439 SequenceNumber::CANCELLED_READ
440 }
441 }
442 None => unreachable!("cancelled transaction should have cancellation info"),
443 };
444 assigned_versions.push(((*id, *initial_shared_version), assigned_version));
445 is_exclusively_accessed_input.push(false);
446 }
447 } else {
448 for (
449 SharedInputObject {
450 id,
451 initial_shared_version,
452 mutability,
453 },
454 assigned_version,
455 ) in shared_input_objects.iter().map(|obj| {
456 (
457 obj,
458 *shared_input_next_versions
459 .get(&obj.id_and_version())
460 .unwrap(),
461 )
462 }) {
463 assigned_versions.push(((*id, *initial_shared_version), assigned_version));
464 input_object_keys.push(ObjectKey(*id, assigned_version));
465 is_exclusively_accessed_input.push(mutability.is_exclusive());
466 }
467 }
468
469 let next_version =
470 SequenceNumber::lamport_increment(input_object_keys.iter().map(|obj| obj.1));
471 assert!(
472 next_version.is_valid(),
473 "Assigned version must be valid. Got {:?}",
474 next_version
475 );
476
477 if !txn_cancelled {
478 assigned_versions
480 .iter()
481 .zip_debug_eq(is_exclusively_accessed_input)
482 .filter_map(|((id, _), mutable)| {
483 if mutable {
484 Some((*id, next_version))
485 } else {
486 None
487 }
488 })
489 .for_each(|(id, version)| {
490 assert!(
491 version.is_valid(),
492 "Assigned version must be a valid version."
493 );
494 shared_input_next_versions
495 .insert(id, version)
496 .expect("Object must exist in shared_input_next_versions.");
497 });
498 }
499
500 trace!(
501 ?tx_key,
502 ?assigned_versions,
503 ?next_version,
504 ?txn_cancelled,
505 "locking shared objects"
506 );
507
508 AssignedVersions::new(assigned_versions, accumulator_version)
509 }
510}
511
512fn get_or_init_versions<'a>(
513 shared_input_objects: impl Iterator<Item = SharedInputObject> + 'a,
514 epoch_store: &AuthorityPerEpochStore,
515 cache_reader: &dyn ObjectCacheRead,
516) -> SuiResult<HashMap<ConsensusObjectSequenceKey, SequenceNumber>> {
517 let mut shared_input_objects: Vec<_> = shared_input_objects
518 .map(|so| so.into_id_and_version())
519 .collect();
520
521 if epoch_store.accumulators_enabled() {
522 shared_input_objects.push((
523 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
524 epoch_store
525 .epoch_start_config()
526 .accumulator_root_obj_initial_shared_version()
527 .expect("accumulator root obj initial shared version should be set"),
528 ));
529 }
530
531 shared_input_objects.sort();
532 shared_input_objects.dedup();
533
534 epoch_store.get_or_init_next_object_versions(&shared_input_objects, cache_reader)
535}
536
537#[cfg(test)]
538mod tests {
539 use super::*;
540
541 use crate::authority::AuthorityState;
542 use crate::authority::shared_object_version_manager::{
543 ConsensusSharedObjVerAssignment, SharedObjVerManager,
544 };
545 use crate::authority::test_authority_builder::TestAuthorityBuilder;
546 use std::collections::{BTreeMap, HashMap};
547 use std::sync::Arc;
548 use sui_protocol_config::ProtocolConfig;
549 use sui_test_transaction_builder::TestTransactionBuilder;
550 use sui_types::base_types::{ObjectID, SequenceNumber, SuiAddress};
551 use sui_types::crypto::{RandomnessRound, get_account_key_pair};
552 use sui_types::digests::ObjectDigest;
553 use sui_types::effects::TestEffectsBuilder;
554 use sui_types::executable_transaction::{
555 CertificateProof, ExecutableTransaction, VerifiedExecutableTransaction,
556 };
557
558 use sui_types::object::Object;
559 use sui_types::transaction::{ObjectArg, SenderSignedData, VerifiedTransaction};
560
561 use sui_types::gas_coin::GAS;
562 use sui_types::transaction::FundsWithdrawalArg;
563 use sui_types::{SUI_ACCUMULATOR_ROOT_OBJECT_ID, SUI_RANDOMNESS_STATE_OBJECT_ID};
564
565 #[tokio::test]
566 async fn test_assign_versions_from_consensus_basic() {
567 let shared_object = Object::shared_for_testing();
568 let id = shared_object.id();
569 let init_shared_version = shared_object.owner.start_version().unwrap();
570 let authority = TestAuthorityBuilder::new()
571 .with_starting_objects(std::slice::from_ref(&shared_object))
572 .build()
573 .await;
574 let certs = [
575 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 3),
576 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, false)], 5),
577 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 9),
578 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 11),
579 ];
580 let epoch_store = authority.epoch_store_for_testing();
581 let assignables = certs
582 .iter()
583 .map(Schedulable::Transaction)
584 .collect::<Vec<_>>();
585 let ConsensusSharedObjVerAssignment {
586 shared_input_next_versions,
587 assigned_versions,
588 } = SharedObjVerManager::assign_versions_from_consensus(
589 &epoch_store,
590 authority.get_object_cache_reader().as_ref(),
591 assignables.iter(),
592 &BTreeMap::new(),
593 )
594 .unwrap();
595 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 .unwrap()
1172 .version();
1173
1174 let withdraw_key = ctx.add_withdraw_transaction();
1175 let settlement_key = ctx.add_settlement_transaction();
1176
1177 let assigned_versions = ctx.assign_versions_from_consensus();
1178 assert_eq!(
1179 assigned_versions,
1180 ConsensusSharedObjVerAssignment {
1181 assigned_versions: AssignedTxAndVersions::new(vec![
1182 (
1183 withdraw_key,
1184 AssignedVersions {
1185 shared_object_versions: vec![],
1186 accumulator_version: Some(acc_version),
1187 }
1188 ),
1189 (
1190 settlement_key,
1191 AssignedVersions {
1192 shared_object_versions: vec![(
1193 (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1194 acc_version
1195 )],
1196 accumulator_version: Some(acc_version),
1197 }
1198 ),
1199 ]),
1200 shared_input_next_versions: HashMap::from([(
1201 (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1202 acc_version.next()
1203 )]),
1204 }
1205 );
1206 }
1207
1208 #[tokio::test]
1209 async fn test_assign_versions_from_consensus_with_multiple_withdraws_and_settlements() {
1210 let mut ctx = WithdrawTestContext::new().await;
1212
1213 let acc_version = ctx
1214 .authority
1215 .get_object(&SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1216 .unwrap()
1217 .version();
1218
1219 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 .unwrap()
1309 .version();
1310
1311 let withdraw_with_shared_key = ctx.add_withdraw_with_shared_object_transaction();
1312 let settlement_key = ctx.add_settlement_transaction();
1313
1314 let assigned_versions = ctx.assign_versions_from_consensus();
1315 assert_eq!(
1316 assigned_versions,
1317 ConsensusSharedObjVerAssignment {
1318 assigned_versions: AssignedTxAndVersions::new(vec![
1319 (
1320 withdraw_with_shared_key,
1321 AssignedVersions {
1322 shared_object_versions: vec![(
1323 (shared_obj_id, shared_obj_version),
1324 shared_obj_version
1325 )],
1326 accumulator_version: Some(acc_version),
1327 }
1328 ),
1329 (
1330 settlement_key,
1331 AssignedVersions {
1332 shared_object_versions: vec![(
1333 (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1334 acc_version
1335 )],
1336 accumulator_version: Some(acc_version),
1337 }
1338 ),
1339 ]),
1340 shared_input_next_versions: HashMap::from([
1341 (
1342 (SUI_ACCUMULATOR_ROOT_OBJECT_ID, acc_version),
1343 acc_version.next()
1344 ),
1345 (
1346 (shared_obj_id, shared_obj_version),
1347 shared_obj_version.next()
1348 ),
1349 ]),
1350 }
1351 );
1352 }
1353}