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 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 let mut assigned_versions = Vec::new();
55 if let Some(round) = randomness_round {
60 let version = shared_input_next_versions
62 .get_mut(&IOTA_RANDOMNESS_STATE_OBJECT_ID)
63 .expect("randomness state object must have been added in get_or_init_versions()");
64 debug!(
65 "assigning shared object versions for randomness: epoch {}, round {round:?} -> version {version:?}",
66 epoch_store.epoch()
67 );
68 assigned_versions.push((
69 TransactionKey::RandomnessRound(epoch_store.epoch(), round),
70 vec![(IOTA_RANDOMNESS_STATE_OBJECT_ID, *version)],
71 ));
72 version.increment();
73 }
74 for cert in certificates {
75 if !cert.contains_shared_object() {
76 continue;
77 }
78 let cert_assigned_versions = Self::assign_versions_for_certificate(
79 cert,
80 &mut shared_input_next_versions,
81 cancelled_txns,
82 epoch_store
83 .protocol_config()
84 .congestion_control_gas_price_feedback_mechanism(),
85 );
86 assigned_versions.push((cert.key(), cert_assigned_versions));
87 }
88
89 Ok(ConsensusSharedObjVerAssignment {
90 shared_input_next_versions,
91 assigned_versions,
92 })
93 }
94
95 pub fn assign_versions_from_effects(
96 certs_and_effects: &[(&VerifiedExecutableTransaction, &TransactionEffects)],
97 epoch_store: &AuthorityPerEpochStore,
98 cache_reader: &dyn ObjectCacheRead,
99 ) -> AssignedTxAndVersions {
100 let _ = get_or_init_versions(
109 certs_and_effects.iter().map(|(cert, _)| cert.data()),
110 epoch_store,
111 cache_reader,
112 false,
113 );
114 let mut assigned_versions = Vec::new();
115 for (cert, effects) in certs_and_effects {
116 let cert_assigned_versions: Vec<_> = effects
117 .input_shared_objects()
118 .into_iter()
119 .map(|iso| iso.id_and_version())
120 .collect();
121 let tx_key = cert.key();
122 trace!(
123 ?tx_key,
124 ?cert_assigned_versions,
125 "locking shared objects from effects"
126 );
127 assigned_versions.push((tx_key, cert_assigned_versions));
128 }
129 assigned_versions
130 }
131
132 pub fn assign_versions_for_certificate(
133 cert: &VerifiedExecutableTransaction,
134 shared_input_next_versions: &mut HashMap<ObjectID, SequenceNumber>,
135 cancelled_txns: &BTreeMap<TransactionDigest, CancelConsensusCertificateReason>,
136 enable_gas_price_feedback: bool,
137 ) -> Vec<(ObjectID, SequenceNumber)> {
138 let tx_digest = cert.digest();
139
140 let cancellation_info = cancelled_txns.get(tx_digest);
142 let congested_objects_info: Option<HashSet<_>> =
143 if let Some(CancelConsensusCertificateReason::CongestionOnObjects {
144 congested_objects,
145 suggested_gas_price: _,
146 }) = &cancellation_info
147 {
148 Some(congested_objects.iter().cloned().collect())
149 } else {
150 None
151 };
152 let txn_cancelled = cancellation_info.is_some();
153
154 let shared_input_objects: Vec<_> = cert.shared_input_objects().collect();
156
157 let mut input_object_keys = transaction_non_shared_input_object_keys(cert)
158 .expect("Transaction input should have been verified");
159 let mut assigned_versions = Vec::with_capacity(shared_input_objects.len());
160 let mut is_mutable_input = Vec::with_capacity(shared_input_objects.len());
161 let receiving_object_keys = transaction_receiving_object_keys(cert);
163 input_object_keys.extend(receiving_object_keys);
164
165 if txn_cancelled {
166 for SharedInputObject { id, .. } in shared_input_objects.iter() {
170 let assigned_version = match cancellation_info {
171 Some(CancelConsensusCertificateReason::CongestionOnObjects {
172 congested_objects: _,
173 suggested_gas_price,
174 }) => {
175 if congested_objects_info
176 .as_ref()
177 .is_some_and(|info| info.contains(id))
178 {
179 if enable_gas_price_feedback {
180 SequenceNumber::new_congested_with_suggested_gas_price(
181 suggested_gas_price.expect(
182 "Suggested gas price for transactions cancelled due \
183 to congestion must not be None if the gas price \
184 feedback is enabled.",
185 ),
186 )
187 } else {
188 SequenceNumber::CONGESTED_PRIOR_TO_GAS_PRICE_FEEDBACK
193 }
194 } else {
195 SequenceNumber::CANCELLED_READ
196 }
197 }
198 Some(CancelConsensusCertificateReason::DkgFailed) => {
199 if id == &IOTA_RANDOMNESS_STATE_OBJECT_ID {
200 SequenceNumber::RANDOMNESS_UNAVAILABLE
201 } else {
202 SequenceNumber::CANCELLED_READ
203 }
204 }
205 None => unreachable!("cancelled transaction should have cancellation info"),
206 };
207 assigned_versions.push((*id, assigned_version));
208 is_mutable_input.push(false);
209 }
210 } else {
211 for (SharedInputObject { id, mutable, .. }, assigned_version) in shared_input_objects
212 .iter()
213 .map(|obj| (obj, *shared_input_next_versions.get(&obj.id()).unwrap()))
214 {
215 assigned_versions.push((*id, assigned_version));
216 input_object_keys.push(ObjectKey(*id, assigned_version));
217 is_mutable_input.push(*mutable);
218 }
219 }
220
221 let next_version =
222 SequenceNumber::lamport_increment(input_object_keys.iter().map(|obj| obj.1));
223 assert!(
224 next_version.is_valid(),
225 "Assigned version must be valid. Got {next_version:?}"
226 );
227
228 if !txn_cancelled {
229 assigned_versions
231 .iter()
232 .zip(is_mutable_input)
233 .filter_map(|((id, _), mutable)| {
234 if mutable {
235 Some((*id, next_version))
236 } else {
237 None
238 }
239 })
240 .for_each(|(id, version)| {
241 assert!(
242 version.is_valid(),
243 "Assigned version must be a valid version."
244 );
245 shared_input_next_versions
246 .insert(id, version)
247 .expect("Object must exist in shared_input_next_versions.");
248 });
249 }
250
251 trace!(
252 ?tx_digest,
253 ?assigned_versions,
254 ?next_version,
255 ?txn_cancelled,
256 "locking shared objects"
257 );
258
259 assigned_versions
260 }
261}
262
263fn get_or_init_versions<'a>(
264 transactions: impl Iterator<Item = &'a SenderSignedData>,
265 epoch_store: &AuthorityPerEpochStore,
266 cache_reader: &dyn ObjectCacheRead,
267 generate_randomness: bool,
268) -> IotaResult<HashMap<ObjectID, SequenceNumber>> {
269 let mut shared_input_objects: Vec<_> = transactions
270 .flat_map(|tx| {
271 tx.transaction_data()
272 .shared_input_objects()
273 .into_iter()
274 .map(|so| so.into_id_and_version())
275 })
276 .collect();
277
278 if generate_randomness {
279 shared_input_objects.push((
280 IOTA_RANDOMNESS_STATE_OBJECT_ID,
281 epoch_store
282 .epoch_start_config()
283 .randomness_obj_initial_shared_version(),
284 ));
285 }
286
287 shared_input_objects.sort();
288 shared_input_objects.dedup();
289
290 epoch_store.get_or_init_next_object_versions(&shared_input_objects, cache_reader)
291}
292
293#[cfg(test)]
294mod tests {
295 use std::collections::{BTreeMap, HashMap};
296
297 use iota_test_transaction_builder::TestTransactionBuilder;
298 use iota_types::{
299 IOTA_RANDOMNESS_STATE_OBJECT_ID,
300 base_types::{IotaAddress, ObjectID, SequenceNumber},
301 crypto::RandomnessRound,
302 digests::ObjectDigest,
303 effects::TestEffectsBuilder,
304 executable_transaction::{
305 CertificateProof, ExecutableTransaction, VerifiedExecutableTransaction,
306 },
307 object::{Object, Owner},
308 programmable_transaction_builder::ProgrammableTransactionBuilder,
309 transaction::{ObjectArg, SenderSignedData, TransactionKey},
310 };
311
312 use super::*;
313 use crate::authority::{
314 epoch_start_configuration::EpochStartConfigTrait,
315 shared_object_version_manager::{ConsensusSharedObjVerAssignment, SharedObjVerManager},
316 test_authority_builder::TestAuthorityBuilder,
317 };
318
319 #[tokio::test]
320 async fn test_assign_versions_from_consensus_basic() {
321 let shared_object = Object::shared_for_testing();
322 let id = shared_object.id();
323 let init_shared_version = match shared_object.owner {
324 Owner::Shared {
325 initial_shared_version,
326 ..
327 } => initial_shared_version,
328 _ => panic!("expected shared object"),
329 };
330 let authority = TestAuthorityBuilder::new()
331 .with_starting_objects(std::slice::from_ref(&shared_object))
332 .build()
333 .await;
334 let certs = vec![
335 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 3),
336 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, false)], 5),
337 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 9),
338 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 11),
339 ];
340 let epoch_store = authority.epoch_store_for_testing();
341 let ConsensusSharedObjVerAssignment {
342 shared_input_next_versions,
343 assigned_versions,
344 } = SharedObjVerManager::assign_versions_from_consensus(
345 &epoch_store,
346 authority.get_object_cache_reader().as_ref(),
347 &certs,
348 None,
349 &BTreeMap::new(),
350 )
351 .unwrap();
352 assert_eq!(
355 epoch_store.get_next_object_version(&id).unwrap(),
356 init_shared_version
357 );
358 assert_eq!(
361 shared_input_next_versions,
362 HashMap::from([(id, SequenceNumber::from_u64(12))])
363 );
364 assert_eq!(
370 assigned_versions,
371 vec![
372 (certs[0].key(), vec![(id, init_shared_version),]),
373 (certs[1].key(), vec![(id, SequenceNumber::from_u64(4)),]),
374 (certs[2].key(), vec![(id, SequenceNumber::from_u64(4)),]),
375 (certs[3].key(), vec![(id, SequenceNumber::from_u64(10)),]),
376 ]
377 );
378 }
379
380 #[tokio::test]
381 async fn test_assign_versions_from_consensus_with_randomness() {
382 let authority = TestAuthorityBuilder::new().build().await;
383 let epoch_store = authority.epoch_store_for_testing();
384 let randomness_obj_version = epoch_store
385 .epoch_start_config()
386 .randomness_obj_initial_shared_version();
387 let certs = vec![
388 generate_shared_objs_tx_with_gas_version(
389 &[(
390 IOTA_RANDOMNESS_STATE_OBJECT_ID,
391 randomness_obj_version,
392 false,
395 )],
396 3,
397 ),
398 generate_shared_objs_tx_with_gas_version(
399 &[(
400 IOTA_RANDOMNESS_STATE_OBJECT_ID,
401 randomness_obj_version,
402 false,
403 )],
404 5,
405 ),
406 ];
407 let ConsensusSharedObjVerAssignment {
408 shared_input_next_versions,
409 assigned_versions,
410 } = SharedObjVerManager::assign_versions_from_consensus(
411 &epoch_store,
412 authority.get_object_cache_reader().as_ref(),
413 &certs,
414 Some(RandomnessRound::new(1)),
415 &BTreeMap::new(),
416 )
417 .unwrap();
418 assert_eq!(
420 epoch_store
421 .get_next_object_version(&IOTA_RANDOMNESS_STATE_OBJECT_ID)
422 .unwrap(),
423 randomness_obj_version
424 );
425 let next_randomness_obj_version = randomness_obj_version.next();
426 assert_eq!(
427 shared_input_next_versions,
428 HashMap::from([(IOTA_RANDOMNESS_STATE_OBJECT_ID, next_randomness_obj_version)])
430 );
431 assert_eq!(
432 assigned_versions,
433 vec![
434 (
435 TransactionKey::RandomnessRound(0, RandomnessRound::new(1)),
436 vec![(IOTA_RANDOMNESS_STATE_OBJECT_ID, randomness_obj_version),]
437 ),
438 (
439 certs[0].key(),
440 vec![(IOTA_RANDOMNESS_STATE_OBJECT_ID, next_randomness_obj_version)]
443 ),
444 (
445 certs[1].key(),
446 vec![(IOTA_RANDOMNESS_STATE_OBJECT_ID, next_randomness_obj_version)]
449 ),
450 ]
451 );
452 }
453
454 #[tokio::test]
456 async fn test_assign_versions_from_consensus_with_cancellation() {
457 let shared_object_1 = Object::shared_for_testing();
458 let shared_object_2 = Object::shared_for_testing();
459 let id1 = shared_object_1.id();
460 let id2 = shared_object_2.id();
461 let init_shared_version_1 = match shared_object_1.owner {
462 Owner::Shared {
463 initial_shared_version,
464 ..
465 } => initial_shared_version,
466 _ => panic!("expected shared object"),
467 };
468 let init_shared_version_2 = match shared_object_2.owner {
469 Owner::Shared {
470 initial_shared_version,
471 ..
472 } => initial_shared_version,
473 _ => panic!("expected shared object"),
474 };
475 let authority = TestAuthorityBuilder::new()
476 .with_starting_objects(&[shared_object_1.clone(), shared_object_2.clone()])
477 .build()
478 .await;
479 let randomness_obj_version = authority
480 .epoch_store_for_testing()
481 .epoch_start_config()
482 .randomness_obj_initial_shared_version();
483
484 let certs = vec![
501 generate_shared_objs_tx_with_gas_version(
502 &[
503 (id1, init_shared_version_1, true),
504 (id2, init_shared_version_2, true),
505 ],
506 3,
507 ),
508 generate_shared_objs_tx_with_gas_version(
509 &[
510 (id1, init_shared_version_1, true),
511 (id2, init_shared_version_2, true),
512 ],
513 5,
514 ),
515 generate_shared_objs_tx_with_gas_version(&[(id1, init_shared_version_1, true)], 1),
516 generate_shared_objs_tx_with_gas_version(
517 &[
518 (id1, init_shared_version_1, true),
519 (id2, init_shared_version_2, true),
520 ],
521 9,
522 ),
523 generate_shared_objs_tx_with_gas_version(
524 &[
525 (
526 IOTA_RANDOMNESS_STATE_OBJECT_ID,
527 randomness_obj_version,
528 false,
529 ),
530 (id2, init_shared_version_2, true),
531 ],
532 11,
533 ),
534 ];
535 let epoch_store = authority.epoch_store_for_testing();
536
537 let suggested_gas_price = 1_000;
539 let cancelled_txns: BTreeMap<TransactionDigest, CancelConsensusCertificateReason> = [
540 (
541 *certs[1].digest(),
542 CancelConsensusCertificateReason::CongestionOnObjects {
543 congested_objects: vec![id1],
544 suggested_gas_price: Some(suggested_gas_price),
545 },
546 ),
547 (
548 *certs[3].digest(),
549 CancelConsensusCertificateReason::CongestionOnObjects {
550 congested_objects: vec![id2],
551 suggested_gas_price: Some(suggested_gas_price),
552 },
553 ),
554 (
555 *certs[4].digest(),
556 CancelConsensusCertificateReason::DkgFailed,
557 ),
558 ]
559 .into_iter()
560 .collect();
561
562 let ConsensusSharedObjVerAssignment {
564 shared_input_next_versions,
565 assigned_versions,
566 } = SharedObjVerManager::assign_versions_from_consensus(
567 &epoch_store,
568 authority.get_object_cache_reader().as_ref(),
569 &certs,
570 None,
571 &cancelled_txns,
572 )
573 .unwrap();
574
575 assert_eq!(
578 shared_input_next_versions,
579 HashMap::from([
580 (id1, SequenceNumber::from_u64(5)), (id2, SequenceNumber::from_u64(4)), (IOTA_RANDOMNESS_STATE_OBJECT_ID, SequenceNumber::from_u64(1)), ])
584 );
585
586 assert_eq!(
588 assigned_versions,
589 vec![
590 (
591 certs[0].key(),
592 vec![(id1, init_shared_version_1), (id2, init_shared_version_2)]
593 ),
594 (
595 certs[1].key(),
596 vec![
597 (
598 id1,
599 SequenceNumber::new_congested_with_suggested_gas_price(
600 suggested_gas_price
601 )
602 ),
603 (id2, SequenceNumber::CANCELLED_READ),
604 ]
605 ),
606 (certs[2].key(), vec![(id1, SequenceNumber::from_u64(4)),]),
607 (
608 certs[3].key(),
609 vec![
610 (id1, SequenceNumber::CANCELLED_READ),
611 (
612 id2,
613 SequenceNumber::new_congested_with_suggested_gas_price(
614 suggested_gas_price
615 )
616 )
617 ]
618 ),
619 (
620 certs[4].key(),
621 vec![
622 (
623 IOTA_RANDOMNESS_STATE_OBJECT_ID,
624 SequenceNumber::RANDOMNESS_UNAVAILABLE
625 ),
626 (id2, SequenceNumber::CANCELLED_READ)
627 ]
628 ),
629 ]
630 );
631 }
632
633 #[tokio::test]
634 async fn test_assign_versions_from_effects() {
635 let shared_object = Object::shared_for_testing();
636 let id = shared_object.id();
637 let init_shared_version = match shared_object.owner {
638 Owner::Shared {
639 initial_shared_version,
640 ..
641 } => initial_shared_version,
642 _ => panic!("expected shared object"),
643 };
644 let authority = TestAuthorityBuilder::new()
645 .with_starting_objects(std::slice::from_ref(&shared_object))
646 .build()
647 .await;
648 let certs = [
649 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 3),
650 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, false)], 5),
651 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 9),
652 generate_shared_objs_tx_with_gas_version(&[(id, init_shared_version, true)], 11),
653 ];
654 let effects = [
655 TestEffectsBuilder::new(certs[0].data()).build(),
656 TestEffectsBuilder::new(certs[1].data())
657 .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(4))]))
658 .build(),
659 TestEffectsBuilder::new(certs[2].data())
660 .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(4))]))
661 .build(),
662 TestEffectsBuilder::new(certs[3].data())
663 .with_shared_input_versions(BTreeMap::from([(id, SequenceNumber::from_u64(10))]))
664 .build(),
665 ];
666 let epoch_store = authority.epoch_store_for_testing();
667 let assigned_versions = SharedObjVerManager::assign_versions_from_effects(
668 certs
669 .iter()
670 .zip(effects.iter())
671 .collect::<Vec<_>>()
672 .as_slice(),
673 &epoch_store,
674 authority.get_object_cache_reader().as_ref(),
675 );
676 assert_eq!(
679 epoch_store.get_next_object_version(&id).unwrap(),
680 init_shared_version
681 );
682 assert_eq!(
683 assigned_versions,
684 vec![
685 (certs[0].key(), vec![(id, init_shared_version),]),
686 (certs[1].key(), vec![(id, SequenceNumber::from_u64(4)),]),
687 (certs[2].key(), vec![(id, SequenceNumber::from_u64(4)),]),
688 (certs[3].key(), vec![(id, SequenceNumber::from_u64(10)),]),
689 ]
690 );
691 }
692
693 fn generate_shared_objs_tx_with_gas_version(
698 shared_objects: &[(ObjectID, SequenceNumber, bool)],
699 gas_object_version: u64,
700 ) -> VerifiedExecutableTransaction {
701 let mut builder = ProgrammableTransactionBuilder::new();
702 for (shared_object_id, shared_object_init_version, shared_object_mutable) in shared_objects
703 {
704 builder
705 .obj(ObjectArg::SharedObject {
706 id: *shared_object_id,
707 initial_shared_version: *shared_object_init_version,
708 mutable: *shared_object_mutable,
709 })
710 .unwrap();
711 }
712 let tx_data = TestTransactionBuilder::new(
713 IotaAddress::ZERO,
714 (
715 ObjectID::random(),
716 SequenceNumber::from_u64(gas_object_version),
717 ObjectDigest::random(),
718 ),
719 0,
720 )
721 .programmable(builder.finish())
722 .build();
723 let tx = SenderSignedData::new(tx_data, vec![]);
724 VerifiedExecutableTransaction::new_unchecked(ExecutableTransaction::new_from_data_and_sig(
725 tx,
726 CertificateProof::new_system(0),
727 ))
728 }
729}