1use std::collections::{BTreeMap, HashMap, HashSet};
6
7use iota_types::{
8 IOTA_RANDOMNESS_STATE_OBJECT_ID,
9 base_types::{ObjectID, SequenceNumber, TransactionDigest},
10 crypto::RandomnessRound,
11 effects::{TransactionEffects, TransactionEffectsAPI},
12 error::IotaResult,
13 executable_transaction::VerifiedExecutableTransaction,
14 storage::{
15 ObjectKey, transaction_non_shared_input_object_keys, transaction_receiving_object_keys,
16 },
17 transaction::{SenderSignedData, SharedInputObject, TransactionDataAPI, TransactionKey},
18};
19use tracing::{debug, trace};
20
21use crate::{
22 authority::{
23 AuthorityPerEpochStore, authority_per_epoch_store::CancelConsensusCertificateReason,
24 epoch_start_configuration::EpochStartConfigTrait,
25 },
26 execution_cache::ObjectCacheRead,
27};
28
29pub struct SharedObjVerManager {}
30
31pub type AssignedTxAndVersions = Vec<(TransactionKey, Vec<(ObjectID, SequenceNumber)>)>;
32
33#[must_use]
34#[derive(Default)]
35pub struct ConsensusSharedObjVerAssignment {
36 pub shared_input_next_versions: HashMap<ObjectID, SequenceNumber>,
37 pub assigned_versions: AssignedTxAndVersions,
38}
39
40impl SharedObjVerManager {
41 pub async fn assign_versions_from_consensus(
42 epoch_store: &AuthorityPerEpochStore,
43 cache_reader: &dyn ObjectCacheRead,
44 certificates: &[VerifiedExecutableTransaction],
45 randomness_round: Option<RandomnessRound>,
46 cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
47 ) -> IotaResult<ConsensusSharedObjVerAssignment> {
48 let mut shared_input_next_versions = get_or_init_versions(
49 certificates.iter().map(|cert| cert.data()),
50 epoch_store,
51 cache_reader,
52 randomness_round.is_some(),
53 )
54 .await?;
55 let mut assigned_versions = Vec::new();
56 if let Some(round) = randomness_round {
61 let version = shared_input_next_versions
63 .get_mut(&IOTA_RANDOMNESS_STATE_OBJECT_ID)
64 .expect("randomness state object must have been added in get_or_init_versions()");
65 debug!(
66 "assigning shared object versions for randomness: epoch {}, round {round:?} -> version {version:?}",
67 epoch_store.epoch()
68 );
69 assigned_versions.push((
70 TransactionKey::RandomnessRound(epoch_store.epoch(), round),
71 vec![(IOTA_RANDOMNESS_STATE_OBJECT_ID, *version)],
72 ));
73 version.increment();
74 }
75 for cert in certificates {
76 if !cert.contains_shared_object() {
77 continue;
78 }
79 let cert_assigned_versions = Self::assign_versions_for_certificate(
80 cert,
81 &mut shared_input_next_versions,
82 cancelled_txns,
83 );
84 assigned_versions.push((cert.key(), cert_assigned_versions));
85 }
86
87 Ok(ConsensusSharedObjVerAssignment {
88 shared_input_next_versions,
89 assigned_versions,
90 })
91 }
92
93 pub async fn assign_versions_from_effects(
94 certs_and_effects: &[(&VerifiedExecutableTransaction, &TransactionEffects)],
95 epoch_store: &AuthorityPerEpochStore,
96 cache_reader: &dyn ObjectCacheRead,
97 ) -> IotaResult<AssignedTxAndVersions> {
98 let _ = get_or_init_versions(
107 certs_and_effects.iter().map(|(cert, _)| cert.data()),
108 epoch_store,
109 cache_reader,
110 false,
111 )
112 .await?;
113 let mut assigned_versions = Vec::new();
114 for (cert, effects) in certs_and_effects {
115 let cert_assigned_versions: Vec<_> = effects
116 .input_shared_objects()
117 .into_iter()
118 .map(|iso| iso.id_and_version())
119 .collect();
120 let tx_key = cert.key();
121 trace!(
122 ?tx_key,
123 ?cert_assigned_versions,
124 "locking shared objects from effects"
125 );
126 assigned_versions.push((tx_key, cert_assigned_versions));
127 }
128 Ok(assigned_versions)
129 }
130
131 pub fn assign_versions_for_certificate(
132 cert: &VerifiedExecutableTransaction,
133 shared_input_next_versions: &mut HashMap<ObjectID, SequenceNumber>,
134 cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
135 ) -> Vec<(ObjectID, SequenceNumber)> {
136 let tx_digest = cert.digest();
137
138 let cancellation_info = cancelled_txns.get(tx_digest);
140 let congested_objects_info: Option<HashSet<_>> =
141 if let Some(CancelConsensusCertificateReason::CongestionOnObjects(congested_objects)) =
142 &cancellation_info
143 {
144 Some(congested_objects.iter().cloned().collect())
145 } else {
146 None
147 };
148 let txn_cancelled = cancellation_info.is_some();
149
150 let shared_input_objects: Vec<_> = cert.shared_input_objects().collect();
152
153 let mut input_object_keys = transaction_non_shared_input_object_keys(cert)
154 .expect("Transaction input should have been verified");
155 let mut assigned_versions = Vec::with_capacity(shared_input_objects.len());
156 let mut is_mutable_input = Vec::with_capacity(shared_input_objects.len());
157 let receiving_object_keys = transaction_receiving_object_keys(cert);
159 input_object_keys.extend(receiving_object_keys);
160
161 if txn_cancelled {
162 for SharedInputObject { id, .. } in shared_input_objects.iter() {
166 let assigned_version = match cancellation_info {
167 Some(CancelConsensusCertificateReason::CongestionOnObjects(_)) => {
168 if congested_objects_info
169 .as_ref()
170 .is_some_and(|info| info.contains(id))
171 {
172 SequenceNumber::CONGESTED
173 } else {
174 SequenceNumber::CANCELLED_READ
175 }
176 }
177 Some(CancelConsensusCertificateReason::DkgFailed) => {
178 if id == &IOTA_RANDOMNESS_STATE_OBJECT_ID {
179 SequenceNumber::RANDOMNESS_UNAVAILABLE
180 } else {
181 SequenceNumber::CANCELLED_READ
182 }
183 }
184 None => unreachable!("cancelled transaction should have cancellation info"),
185 };
186 assigned_versions.push((*id, assigned_version));
187 is_mutable_input.push(false);
188 }
189 } else {
190 for (SharedInputObject { id, mutable, .. }, assigned_version) in shared_input_objects
191 .iter()
192 .map(|obj| (obj, *shared_input_next_versions.get(&obj.id()).unwrap()))
193 {
194 assigned_versions.push((*id, assigned_version));
195 input_object_keys.push(ObjectKey(*id, assigned_version));
196 is_mutable_input.push(*mutable);
197 }
198 }
199
200 let next_version =
201 SequenceNumber::lamport_increment(input_object_keys.iter().map(|obj| obj.1));
202 assert!(
203 next_version.is_valid(),
204 "Assigned version must be valid. Got {:?}",
205 next_version
206 );
207
208 if !txn_cancelled {
209 assigned_versions
211 .iter()
212 .zip(is_mutable_input)
213 .filter_map(|((id, _), mutable)| {
214 if mutable {
215 Some((*id, next_version))
216 } else {
217 None
218 }
219 })
220 .for_each(|(id, version)| {
221 assert!(
222 version.is_valid(),
223 "Assigned version must be a valid version."
224 );
225 shared_input_next_versions
226 .insert(id, version)
227 .expect("Object must exist in shared_input_next_versions.");
228 });
229 }
230
231 trace!(
232 ?tx_digest,
233 ?assigned_versions,
234 ?next_version,
235 ?txn_cancelled,
236 "locking shared objects"
237 );
238
239 assigned_versions
240 }
241}
242
243async fn get_or_init_versions(
244 transactions: impl Iterator<Item = &SenderSignedData>,
245 epoch_store: &AuthorityPerEpochStore,
246 cache_reader: &dyn ObjectCacheRead,
247 generate_randomness: bool,
248) -> IotaResult<HashMap<ObjectID, SequenceNumber>> {
249 let mut shared_input_objects: Vec<_> = transactions
250 .flat_map(|tx| {
251 tx.transaction_data()
252 .shared_input_objects()
253 .into_iter()
254 .map(|so| so.into_id_and_version())
255 })
256 .collect();
257
258 if generate_randomness {
259 shared_input_objects.push((
260 IOTA_RANDOMNESS_STATE_OBJECT_ID,
261 epoch_store
262 .epoch_start_config()
263 .randomness_obj_initial_shared_version(),
264 ));
265 }
266
267 shared_input_objects.sort();
268 shared_input_objects.dedup();
269
270 epoch_store
271 .get_or_init_next_object_versions(&shared_input_objects, cache_reader)
272 .await
273}
274
275#[cfg(test)]
276mod tests {
277 use std::collections::{BTreeMap, HashMap};
278
279 use iota_test_transaction_builder::TestTransactionBuilder;
280 use iota_types::{
281 IOTA_RANDOMNESS_STATE_OBJECT_ID,
282 base_types::{IotaAddress, ObjectID, SequenceNumber},
283 crypto::RandomnessRound,
284 digests::ObjectDigest,
285 effects::TestEffectsBuilder,
286 executable_transaction::{
287 CertificateProof, ExecutableTransaction, VerifiedExecutableTransaction,
288 },
289 object::{Object, Owner},
290 programmable_transaction_builder::ProgrammableTransactionBuilder,
291 transaction::{ObjectArg, SenderSignedData, TransactionKey},
292 };
293
294 use super::*;
295 use crate::authority::{
296 epoch_start_configuration::EpochStartConfigTrait,
297 shared_object_version_manager::{ConsensusSharedObjVerAssignment, SharedObjVerManager},
298 test_authority_builder::TestAuthorityBuilder,
299 };
300
301 #[tokio::test]
302 async fn test_assign_versions_from_consensus_basic() {
303 let shared_object = Object::shared_for_testing();
304 let id = shared_object.id();
305 let init_shared_version = match shared_object.owner {
306 Owner::Shared {
307 initial_shared_version,
308 ..
309 } => initial_shared_version,
310 _ => panic!("expected shared object"),
311 };
312 let authority = TestAuthorityBuilder::new()
313 .with_starting_objects(&[shared_object.clone()])
314 .build()
315 .await;
316 let certs = vec![
317 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 3),
318 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, false)], 5),
319 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 9),
320 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 11),
321 ];
322 let epoch_store = authority.epoch_store_for_testing();
323 let ConsensusSharedObjVerAssignment {
324 shared_input_next_versions,
325 assigned_versions,
326 } = SharedObjVerManager::assign_versions_from_consensus(
327 &epoch_store,
328 authority.get_object_cache_reader().as_ref(),
329 &certs,
330 None,
331 &BTreeMap::new(),
332 )
333 .await
334 .unwrap();
335 assert_eq!(
338 epoch_store.get_next_object_version(&id).unwrap(),
339 init_shared_version
340 );
341 assert_eq!(
344 shared_input_next_versions,
345 HashMap::from([(id, SequenceNumber::from_u64(12))])
346 );
347 assert_eq!(
353 assigned_versions,
354 vec![
355 (certs[0].key(), vec![(id, init_shared_version),]),
356 (certs[1].key(), vec![(id, SequenceNumber::from_u64(4)),]),
357 (certs[2].key(), vec![(id, SequenceNumber::from_u64(4)),]),
358 (certs[3].key(), vec![(id, SequenceNumber::from_u64(10)),]),
359 ]
360 );
361 }
362
363 #[tokio::test]
364 async fn test_assign_versions_from_consensus_with_randomness() {
365 let authority = TestAuthorityBuilder::new().build().await;
366 let epoch_store = authority.epoch_store_for_testing();
367 let randomness_obj_version = epoch_store
368 .epoch_start_config()
369 .randomness_obj_initial_shared_version();
370 let certs = vec![
371 generate_shared_objs_tx_with_gas_version(
372 &[(
373 IOTA_RANDOMNESS_STATE_OBJECT_ID,
374 randomness_obj_version,
375 false,
378 )],
379 3,
380 ),
381 generate_shared_objs_tx_with_gas_version(
382 &[(
383 IOTA_RANDOMNESS_STATE_OBJECT_ID,
384 randomness_obj_version,
385 false,
386 )],
387 5,
388 ),
389 ];
390 let ConsensusSharedObjVerAssignment {
391 shared_input_next_versions,
392 assigned_versions,
393 } = SharedObjVerManager::assign_versions_from_consensus(
394 &epoch_store,
395 authority.get_object_cache_reader().as_ref(),
396 &certs,
397 Some(RandomnessRound::new(1)),
398 &BTreeMap::new(),
399 )
400 .await
401 .unwrap();
402 assert_eq!(
404 epoch_store
405 .get_next_object_version(&IOTA_RANDOMNESS_STATE_OBJECT_ID)
406 .unwrap(),
407 randomness_obj_version
408 );
409 let next_randomness_obj_version = randomness_obj_version.next();
410 assert_eq!(
411 shared_input_next_versions,
412 HashMap::from([(IOTA_RANDOMNESS_STATE_OBJECT_ID, next_randomness_obj_version)])
414 );
415 assert_eq!(
416 assigned_versions,
417 vec![
418 (
419 TransactionKey::RandomnessRound(0, RandomnessRound::new(1)),
420 vec![(IOTA_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),]
421 ),
422 (
423 certs[0].key(),
424 vec![(IOTA_RANDOMNESS_STATE_OBJECT_ID, next_randomness_obj_version)]
427 ),
428 (
429 certs[1].key(),
430 vec![(IOTA_RANDOMNESS_STATE_OBJECT_ID, next_randomness_obj_version)]
433 ),
434 ]
435 );
436 }
437
438 #[tokio::test]
440 async fn test_assign_versions_from_consensus_with_cancellation() {
441 let shared_object_1 = Object::shared_for_testing();
442 let shared_object_2 = Object::shared_for_testing();
443 let id1 = shared_object_1.id();
444 let id2 = shared_object_2.id();
445 let init_shared_version_1 = match shared_object_1.owner {
446 Owner::Shared {
447 initial_shared_version,
448 ..
449 } => initial_shared_version,
450 _ => panic!("expected shared object"),
451 };
452 let init_shared_version_2 = match shared_object_2.owner {
453 Owner::Shared {
454 initial_shared_version,
455 ..
456 } => initial_shared_version,
457 _ => panic!("expected shared object"),
458 };
459 let authority = TestAuthorityBuilder::new()
460 .with_starting_objects(&[shared_object_1.clone(), shared_object_2.clone()])
461 .build()
462 .await;
463 let randomness_obj_version = authority
464 .epoch_store_for_testing()
465 .epoch_start_config()
466 .randomness_obj_initial_shared_version();
467
468 let certs = vec![
485 generate_shared_objs_tx_with_gas_version(
486 &[
487 (id1, init_shared_version_1, true),
488 (id2, init_shared_version_2, true),
489 ],
490 3,
491 ),
492 generate_shared_objs_tx_with_gas_version(
493 &[
494 (id1, init_shared_version_1, true),
495 (id2, init_shared_version_2, true),
496 ],
497 5,
498 ),
499 generate_shared_objs_tx_with_gas_version(&[(id1, init_shared_version_1, true)], 1),
500 generate_shared_objs_tx_with_gas_version(
501 &[
502 (id1, init_shared_version_1, true),
503 (id2, init_shared_version_2, true),
504 ],
505 9,
506 ),
507 generate_shared_objs_tx_with_gas_version(
508 &[
509 (
510 IOTA_RANDOMNESS_STATE_OBJECT_ID,
511 randomness_obj_version,
512 false,
513 ),
514 (id2, init_shared_version_2, true),
515 ],
516 11,
517 ),
518 ];
519 let epoch_store = authority.epoch_store_for_testing();
520
521 let cancelled_txns: BTreeMap<TransactionDigest, CancelConsensusCertificateReason> = [
523 (
524 *certs[1].digest(),
525 CancelConsensusCertificateReason::CongestionOnObjects(vec![id1]),
526 ),
527 (
528 *certs[3].digest(),
529 CancelConsensusCertificateReason::CongestionOnObjects(vec![id2]),
530 ),
531 (
532 *certs[4].digest(),
533 CancelConsensusCertificateReason::DkgFailed,
534 ),
535 ]
536 .into_iter()
537 .collect();
538
539 let ConsensusSharedObjVerAssignment {
541 shared_input_next_versions,
542 assigned_versions,
543 } = SharedObjVerManager::assign_versions_from_consensus(
544 &epoch_store,
545 authority.get_object_cache_reader().as_ref(),
546 &certs,
547 None,
548 &cancelled_txns,
549 )
550 .await
551 .unwrap();
552
553 assert_eq!(
556 shared_input_next_versions,
557 HashMap::from([
558 (id1, SequenceNumber::from_u64(5)), (id2, SequenceNumber::from_u64(4)), (IOTA_RANDOMNESS_STATE_OBJECT_ID, SequenceNumber::from_u64(1)), ])
562 );
563
564 assert_eq!(
566 assigned_versions,
567 vec![
568 (
569 certs[0].key(),
570 vec![(id1, init_shared_version_1), (id2, init_shared_version_2)]
571 ),
572 (
573 certs[1].key(),
574 vec![
575 (id1, SequenceNumber::CONGESTED),
576 (id2, SequenceNumber::CANCELLED_READ),
577 ]
578 ),
579 (certs[2].key(), vec![(id1, SequenceNumber::from_u64(4)),]),
580 (
581 certs[3].key(),
582 vec![
583 (id1, SequenceNumber::CANCELLED_READ),
584 (id2, SequenceNumber::CONGESTED)
585 ]
586 ),
587 (
588 certs[4].key(),
589 vec![
590 (
591 IOTA_RANDOMNESS_STATE_OBJECT_ID,
592 SequenceNumber::RANDOMNESS_UNAVAILABLE
593 ),
594 (id2, SequenceNumber::CANCELLED_READ)
595 ]
596 ),
597 ]
598 );
599 }
600
601 #[tokio::test]
602 async fn test_assign_versions_from_effects() {
603 let shared_object = Object::shared_for_testing();
604 let id = shared_object.id();
605 let init_shared_version = match shared_object.owner {
606 Owner::Shared {
607 initial_shared_version,
608 ..
609 } => initial_shared_version,
610 _ => panic!("expected shared object"),
611 };
612 let authority = TestAuthorityBuilder::new()
613 .with_starting_objects(&[shared_object.clone()])
614 .build()
615 .await;
616 let certs = vec![
617 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 3),
618 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, false)], 5),
619 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 9),
620 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 11),
621 ];
622 let effects = vec![
623 TestEffectsBuilder::new(certs[0].data()).build(),
624 TestEffectsBuilder::new(certs[1].data())
625 .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(4))]))
626 .build(),
627 TestEffectsBuilder::new(certs[2].data())
628 .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(4))]))
629 .build(),
630 TestEffectsBuilder::new(certs[3].data())
631 .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(10))]))
632 .build(),
633 ];
634 let epoch_store = authority.epoch_store_for_testing();
635 let assigned_versions = SharedObjVerManager::assign_versions_from_effects(
636 certs
637 .iter()
638 .zip(effects.iter())
639 .collect::<Vec<_>>()
640 .as_slice(),
641 &epoch_store,
642 authority.get_object_cache_reader().as_ref(),
643 )
644 .await
645 .unwrap();
646 assert_eq!(
649 epoch_store.get_next_object_version(&id).unwrap(),
650 init_shared_version
651 );
652 assert_eq!(
653 assigned_versions,
654 vec![
655 (certs[0].key(), vec![(id, init_shared_version),]),
656 (certs[1].key(), vec![(id, SequenceNumber::from_u64(4)),]),
657 (certs[2].key(), vec![(id, SequenceNumber::from_u64(4)),]),
658 (certs[3].key(), vec![(id, SequenceNumber::from_u64(10)),]),
659 ]
660 );
661 }
662
663 fn generate_shared_objs_tx_with_gas_version(
668 shared_objects: &[(ObjectID, SequenceNumber, bool)],
669 gas_object_version: u64,
670 ) -> VerifiedExecutableTransaction {
671 let mut builder = ProgrammableTransactionBuilder::new();
672 for (shared_object_id, shared_object_init_version, shared_object_mutable) in shared_objects
673 {
674 builder
675 .obj(ObjectArg::SharedObject {
676 id: *shared_object_id,
677 initial_shared_version: *shared_object_init_version,
678 mutable: *shared_object_mutable,
679 })
680 .unwrap();
681 }
682 let tx_data = TestTransactionBuilder::new(
683 IotaAddress::ZERO,
684 (
685 ObjectID::random(),
686 SequenceNumber::from_u64(gas_object_version),
687 ObjectDigest::random(),
688 ),
689 0,
690 )
691 .programmable(builder.finish())
692 .build();
693 let tx = SenderSignedData::new(tx_data, vec![]);
694 VerifiedExecutableTransaction::new_unchecked(ExecutableTransaction::new_from_data_and_sig(
695 tx,
696 CertificateProof::new_system(0),
697 ))
698 }
699}