1pub mod deny;
6
7pub use checked::*;
8
9#[iota_macros::with_checked_arithmetic]
10mod checked {
11 use std::{
12 collections::{BTreeMap, HashSet},
13 sync::Arc,
14 };
15
16 use iota_config::verifier_signing_config::VerifierSigningConfig;
17 use iota_protocol_config::ProtocolConfig;
18 use iota_types::{
19 IOTA_AUTHENTICATOR_STATE_OBJECT_ID, IOTA_CLOCK_OBJECT_ID, IOTA_CLOCK_OBJECT_SHARED_VERSION,
20 IOTA_RANDOMNESS_STATE_OBJECT_ID,
21 base_types::{IotaAddress, ObjectID, ObjectRef, SequenceNumber},
22 error::{IotaError, IotaResult, UserInputError, UserInputResult},
23 executable_transaction::VerifiedExecutableTransaction,
24 fp_bail, fp_ensure,
25 gas::IotaGasStatus,
26 metrics::BytecodeVerifierMetrics,
27 object::{Object, Owner},
28 transaction::{
29 CheckedInputObjects, InputObjectKind, InputObjects, ObjectReadResult,
30 ObjectReadResultKind, ReceivingObjectReadResult, ReceivingObjects, TransactionData,
31 TransactionDataAPI, TransactionKind,
32 },
33 };
34 use tracing::{error, instrument};
35
36 trait IntoChecked {
37 fn into_checked(self) -> CheckedInputObjects;
38 }
39
40 impl IntoChecked for InputObjects {
41 fn into_checked(self) -> CheckedInputObjects {
42 CheckedInputObjects::new_with_checked_transaction_inputs(self)
43 }
44 }
45
46 fn get_gas_status(
51 objects: &InputObjects,
52 gas: &[ObjectRef],
53 protocol_config: &ProtocolConfig,
54 reference_gas_price: u64,
55 transaction: &TransactionData,
56 authentication_gas_budget: u64,
57 ) -> IotaResult<IotaGasStatus> {
58 if transaction.is_system_tx() {
59 Ok(IotaGasStatus::new_unmetered())
60 } else {
61 let transaction_gas_budget = transaction.gas_budget();
62
63 let gas_budget = transaction_gas_budget + authentication_gas_budget;
66
67 check_gas(
68 objects,
69 protocol_config,
70 reference_gas_price,
71 gas,
72 gas_budget,
73 transaction.gas_price(),
74 )
75 }
76 }
77
78 #[instrument(level = "trace", skip_all, fields(tx_digest = ?transaction.digest()))]
79 pub fn check_transaction_input(
80 protocol_config: &ProtocolConfig,
81 reference_gas_price: u64,
82 transaction: &TransactionData,
83 input_objects: InputObjects,
84 receiving_objects: &ReceivingObjects,
85 metrics: &Arc<BytecodeVerifierMetrics>,
86 verifier_signing_config: &VerifierSigningConfig,
87 authentication_gas_budget: u64,
88 ) -> IotaResult<(IotaGasStatus, CheckedInputObjects)> {
89 let gas_status = check_transaction_input_inner(
90 protocol_config,
91 reference_gas_price,
92 transaction,
93 &input_objects,
94 &[],
95 authentication_gas_budget,
96 )?;
97 check_receiving_objects(&input_objects, receiving_objects)?;
98 check_non_system_packages_to_be_published(
100 transaction,
101 protocol_config,
102 metrics,
103 verifier_signing_config,
104 )?;
105
106 Ok((gas_status, input_objects.into_checked()))
107 }
108
109 #[instrument(level = "trace", skip_all, fields(tx_digest = ?transaction.digest()))]
110 pub fn check_transaction_input_with_given_gas(
111 protocol_config: &ProtocolConfig,
112 reference_gas_price: u64,
113 transaction: &TransactionData,
114 mut input_objects: InputObjects,
115 receiving_objects: ReceivingObjects,
116 gas_object: Object,
117 metrics: &Arc<BytecodeVerifierMetrics>,
118 verifier_signing_config: &VerifierSigningConfig,
119 ) -> IotaResult<(IotaGasStatus, CheckedInputObjects)> {
120 let gas_object_ref = gas_object.compute_object_reference();
121 input_objects.push(ObjectReadResult::new_from_gas_object(&gas_object));
122
123 let gas_status = check_transaction_input_inner(
124 protocol_config,
125 reference_gas_price,
126 transaction,
127 &input_objects,
128 &[gas_object_ref],
129 0,
130 )?;
131 check_receiving_objects(&input_objects, &receiving_objects)?;
132 check_non_system_packages_to_be_published(
134 transaction,
135 protocol_config,
136 metrics,
137 verifier_signing_config,
138 )?;
139
140 Ok((gas_status, input_objects.into_checked()))
141 }
142
143 #[instrument(level = "trace", skip_all)]
149 pub fn check_certificate_input(
150 cert: &VerifiedExecutableTransaction,
151 input_objects: InputObjects,
152 protocol_config: &ProtocolConfig,
153 reference_gas_price: u64,
154 ) -> IotaResult<(IotaGasStatus, CheckedInputObjects)> {
155 let transaction = cert.data().transaction_data();
156 let gas_status = check_transaction_input_inner(
157 protocol_config,
158 reference_gas_price,
159 transaction,
160 &input_objects,
161 &[],
162 0,
163 )?;
164 Ok((gas_status, input_objects.into_checked()))
169 }
170
171 #[instrument(level = "trace", skip_all)]
174 pub fn check_dev_inspect_input(
175 config: &ProtocolConfig,
176 kind: &TransactionKind,
177 input_objects: InputObjects,
178 _receiving_objects: ReceivingObjects,
180 ) -> IotaResult<CheckedInputObjects> {
181 kind.validity_check(config)?;
182 if kind.is_system_tx() {
183 return Err(UserInputError::Unsupported(format!(
184 "Transaction kind {kind} is not supported in dev-inspect"
185 ))
186 .into());
187 }
188 let mut used_objects: HashSet<IotaAddress> = HashSet::new();
189 for input_object in input_objects.iter() {
190 let Some(object) = input_object.as_object() else {
191 continue;
193 };
194
195 if !object.is_immutable() {
196 fp_ensure!(
197 used_objects.insert(object.id().into()),
198 UserInputError::MutableObjectUsedMoreThanOnce {
199 object_id: object.id()
200 }
201 .into()
202 );
203 }
204 }
205
206 Ok(input_objects.into_checked())
207 }
208
209 #[instrument(level = "trace", skip_all)]
215 pub fn check_move_authenticator_input_for_signing(
216 authenticator_input_objects: InputObjects,
217 ) -> IotaResult<CheckedInputObjects> {
218 check_move_authenticator_objects(&authenticator_input_objects)?;
219
220 Ok(authenticator_input_objects.into_checked())
221 }
222
223 #[instrument(level = "trace", skip_all)]
235 pub fn check_certificate_and_move_authenticator_input(
236 cert: &VerifiedExecutableTransaction,
237 tx_input_objects: InputObjects,
238 authenticator_input_objects: InputObjects,
239 authenticator_gas_budget: u64,
240 protocol_config: &ProtocolConfig,
241 reference_gas_price: u64,
242 ) -> IotaResult<(IotaGasStatus, CheckedInputObjects, CheckedInputObjects)> {
243 check_move_authenticator_objects(&authenticator_input_objects)?;
245
246 let transaction = cert.data().transaction_data();
248 let gas_status = check_transaction_input_inner(
249 protocol_config,
250 reference_gas_price,
251 transaction,
252 &tx_input_objects,
253 &[],
254 authenticator_gas_budget,
255 )?;
256
257 let authenticator_input_objects = authenticator_input_objects.into_checked();
259 let input_objects_union = checked_input_objects_union(
260 tx_input_objects.into_checked(),
261 &authenticator_input_objects,
262 )?;
263
264 Ok((gas_status, authenticator_input_objects, input_objects_union))
265 }
266
267 fn check_transaction_input_inner(
269 protocol_config: &ProtocolConfig,
270 reference_gas_price: u64,
271 transaction: &TransactionData,
272 input_objects: &InputObjects,
273 gas_override: &[ObjectRef],
275 authentication_gas_budget: u64,
276 ) -> IotaResult<IotaGasStatus> {
277 let gas = if gas_override.is_empty() {
279 transaction.gas()
280 } else {
281 gas_override
282 };
283
284 let gas_status = get_gas_status(
285 input_objects,
286 gas,
287 protocol_config,
288 reference_gas_price,
289 transaction,
290 authentication_gas_budget,
291 )?;
292 check_objects(transaction, input_objects)?;
293
294 Ok(gas_status)
295 }
296
297 #[instrument(level = "trace", skip_all)]
298 fn check_receiving_objects(
299 input_objects: &InputObjects,
300 receiving_objects: &ReceivingObjects,
301 ) -> Result<(), IotaError> {
302 let mut objects_in_txn: HashSet<_> = input_objects
303 .object_kinds()
304 .map(|x| x.object_id())
305 .collect();
306
307 for ReceivingObjectReadResult {
316 object_ref: (object_id, version, object_digest),
317 object,
318 } in receiving_objects.iter()
319 {
320 fp_ensure!(
321 *version < SequenceNumber::MAX_VALID_EXCL,
322 UserInputError::InvalidSequenceNumber.into()
323 );
324
325 let Some(object) = object.as_object() else {
326 continue;
328 };
329
330 if !(object.owner.is_address_owned()
331 && object.version() == *version
332 && object.digest() == *object_digest)
333 {
334 fp_ensure!(
336 object.version() == *version,
337 UserInputError::ObjectVersionUnavailableForConsumption {
338 provided_obj_ref: (*object_id, *version, *object_digest),
339 current_version: object.version(),
340 }
341 .into()
342 );
343
344 fp_ensure!(
346 !object.is_package(),
347 UserInputError::MovePackageAsObject {
348 object_id: *object_id
349 }
350 .into()
351 );
352
353 let expected_digest = object.digest();
355 fp_ensure!(
356 expected_digest == *object_digest,
357 UserInputError::InvalidObjectDigest {
358 object_id: *object_id,
359 expected_digest
360 }
361 .into()
362 );
363
364 match object.owner {
365 Owner::AddressOwner(_) => {
366 debug_assert!(
367 false,
368 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
369 (*object_id, *version, *object_id),
370 object
371 );
372 error!(
373 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
374 (*object_id, *version, *object_id),
375 object
376 );
377 fp_bail!(
380 UserInputError::ObjectNotFound {
381 object_id: *object_id,
382 version: Some(*version),
383 }
384 .into()
385 )
386 }
387 Owner::ObjectOwner(owner) => {
388 fp_bail!(
389 UserInputError::InvalidChildObjectArgument {
390 child_id: object.id(),
391 parent_id: owner.into(),
392 }
393 .into()
394 )
395 }
396 Owner::Shared { .. } => fp_bail!(UserInputError::NotSharedObject.into()),
397 Owner::Immutable => fp_bail!(
398 UserInputError::MutableParameterExpected {
399 object_id: *object_id
400 }
401 .into()
402 ),
403 };
404 }
405
406 fp_ensure!(
407 !objects_in_txn.contains(object_id),
408 UserInputError::DuplicateObjectRefInput.into()
409 );
410
411 objects_in_txn.insert(*object_id);
412 }
413 Ok(())
414 }
415
416 #[instrument(level = "trace", skip_all)]
419 fn check_gas(
420 objects: &InputObjects,
421 protocol_config: &ProtocolConfig,
422 reference_gas_price: u64,
423 gas: &[ObjectRef],
424 gas_budget: u64,
425 gas_price: u64,
426 ) -> IotaResult<IotaGasStatus> {
427 let gas_status =
428 IotaGasStatus::new(gas_budget, gas_price, reference_gas_price, protocol_config)?;
429
430 let objects: BTreeMap<_, _> = objects.iter().map(|o| (o.id(), o)).collect();
433 let mut gas_objects = vec![];
434 for obj_ref in gas {
435 let obj = objects.get(&obj_ref.0);
436 let obj = *obj.ok_or(UserInputError::ObjectNotFound {
437 object_id: obj_ref.0,
438 version: Some(obj_ref.1),
439 })?;
440 gas_objects.push(obj);
441 }
442 gas_status.check_gas_balance(&gas_objects, gas_budget)?;
443 Ok(gas_status)
444 }
445
446 #[instrument(level = "trace", skip_all)]
449 fn check_objects(transaction: &TransactionData, objects: &InputObjects) -> UserInputResult<()> {
450 let mut used_objects: HashSet<IotaAddress> = HashSet::new();
452 for object in objects.iter() {
453 if object.is_mutable() {
454 fp_ensure!(
455 used_objects.insert(object.id().into()),
456 UserInputError::MutableObjectUsedMoreThanOnce {
457 object_id: object.id()
458 }
459 );
460 }
461 }
462
463 if !transaction.is_genesis_tx() && objects.is_empty() {
464 return Err(UserInputError::ObjectInputArityViolation);
465 }
466
467 let gas_coins: HashSet<ObjectID> =
468 HashSet::from_iter(transaction.gas().iter().map(|obj_ref| obj_ref.0));
469 for object in objects.iter() {
470 let input_object_kind = object.input_object_kind;
471
472 match &object.object {
473 ObjectReadResultKind::Object(object) => {
474 let owner_address = if gas_coins.contains(&object.id()) {
476 transaction.gas_owner()
477 } else {
478 transaction.sender()
479 };
480 let system_transaction = transaction.is_system_tx();
483 check_one_object(
484 &owner_address,
485 input_object_kind,
486 object,
487 system_transaction,
488 )?;
489 }
490 ObjectReadResultKind::DeletedSharedObject(_, _) => (),
492 ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
495 }
496 }
497
498 Ok(())
499 }
500
501 fn check_one_object(
503 owner: &IotaAddress,
504 object_kind: InputObjectKind,
505 object: &Object,
506 system_transaction: bool,
507 ) -> UserInputResult {
508 match object_kind {
509 InputObjectKind::MovePackage(package_id) => {
510 fp_ensure!(
511 object.data.try_as_package().is_some(),
512 UserInputError::MoveObjectAsPackage {
513 object_id: package_id
514 }
515 );
516 }
517 InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
518 fp_ensure!(
519 !object.is_package(),
520 UserInputError::MovePackageAsObject { object_id }
521 );
522 fp_ensure!(
523 sequence_number < SequenceNumber::MAX_VALID_EXCL,
524 UserInputError::InvalidSequenceNumber
525 );
526
527 assert_eq!(
529 object.version(),
530 sequence_number,
531 "The fetched object version {} does not match the requested version {}, object id: {}",
532 object.version(),
533 sequence_number,
534 object.id(),
535 );
536
537 let expected_digest = object.digest();
539 fp_ensure!(
540 expected_digest == object_digest,
541 UserInputError::InvalidObjectDigest {
542 object_id,
543 expected_digest
544 }
545 );
546
547 match object.owner {
548 Owner::Immutable => {
549 }
551 Owner::AddressOwner(actual_owner) => {
552 fp_ensure!(
554 owner == &actual_owner,
555 UserInputError::IncorrectUserSignature {
556 error: format!(
557 "Object {object_id:?} is owned by account address {actual_owner}, but given owner/signer address is {owner}"
558 ),
559 }
560 );
561 }
562 Owner::ObjectOwner(owner) => {
563 return Err(UserInputError::InvalidChildObjectArgument {
564 child_id: object.id(),
565 parent_id: owner.into(),
566 });
567 }
568 Owner::Shared { .. } => {
569 return Err(UserInputError::NotSharedObject);
572 }
573 };
574 }
575 InputObjectKind::SharedMoveObject {
576 id: IOTA_CLOCK_OBJECT_ID,
577 initial_shared_version: IOTA_CLOCK_OBJECT_SHARED_VERSION,
578 mutable: true,
579 } => {
580 if system_transaction {
583 return Ok(());
584 } else {
585 return Err(UserInputError::ImmutableParameterExpected {
586 object_id: IOTA_CLOCK_OBJECT_ID,
587 });
588 }
589 }
590 InputObjectKind::SharedMoveObject {
591 id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
592 ..
593 } => {
594 if system_transaction {
595 return Ok(());
596 } else {
597 return Err(UserInputError::InaccessibleSystemObject {
598 object_id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
599 });
600 }
601 }
602 InputObjectKind::SharedMoveObject {
603 id: IOTA_RANDOMNESS_STATE_OBJECT_ID,
604 mutable: true,
605 ..
606 } => {
607 if system_transaction {
610 return Ok(());
611 } else {
612 return Err(UserInputError::ImmutableParameterExpected {
613 object_id: IOTA_RANDOMNESS_STATE_OBJECT_ID,
614 });
615 }
616 }
617 InputObjectKind::SharedMoveObject {
618 initial_shared_version: input_initial_shared_version,
619 ..
620 } => {
621 fp_ensure!(
622 object.version() < SequenceNumber::MAX_VALID_EXCL,
623 UserInputError::InvalidSequenceNumber
624 );
625
626 match object.owner {
627 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
628 return Err(UserInputError::NotSharedObject);
630 }
631 Owner::Shared {
632 initial_shared_version: actual_initial_shared_version,
633 } => {
634 fp_ensure!(
635 input_initial_shared_version == actual_initial_shared_version,
636 UserInputError::SharedObjectStartingVersionMismatch
637 )
638 }
639 }
640 }
641 };
642 Ok(())
643 }
644
645 #[instrument(level = "trace", skip_all)]
648 fn check_move_authenticator_objects(
649 authenticator_objects: &InputObjects,
650 ) -> UserInputResult<()> {
651 for object in authenticator_objects.iter() {
652 let input_object_kind = object.input_object_kind;
653
654 match &object.object {
655 ObjectReadResultKind::Object(object) => {
656 check_one_move_authenticator_object(input_object_kind, object)?;
657 }
658 ObjectReadResultKind::DeletedSharedObject(_, _) => (),
660 ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
663 }
664 }
665
666 Ok(())
667 }
668
669 fn check_one_move_authenticator_object(
671 object_kind: InputObjectKind,
672 object: &Object,
673 ) -> UserInputResult {
674 match object_kind {
675 InputObjectKind::MovePackage(package_id) => {
676 return Err(UserInputError::PackageIsInMoveAuthenticatorInput { package_id });
677 }
678 InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
679 fp_ensure!(
680 !object.is_package(),
681 UserInputError::MovePackageAsObject { object_id }
682 );
683 fp_ensure!(
684 sequence_number < SequenceNumber::MAX_VALID_EXCL,
685 UserInputError::InvalidSequenceNumber
686 );
687
688 assert_eq!(
690 object.version(),
691 sequence_number,
692 "The fetched object version {} does not match the requested version {}, object id: {}",
693 object.version(),
694 sequence_number,
695 object.id(),
696 );
697
698 let expected_digest = object.digest();
700 fp_ensure!(
701 expected_digest == object_digest,
702 UserInputError::InvalidObjectDigest {
703 object_id,
704 expected_digest
705 }
706 );
707
708 match object.owner {
709 Owner::Immutable => {
710 }
712 Owner::AddressOwner { .. } => {
713 return Err(UserInputError::AddressOwnedIsInMoveAuthenticatorInput {
714 object_id: object.id(),
715 });
716 }
717 Owner::ObjectOwner { .. } => {
718 return Err(UserInputError::ObjectOwnedIsInMoveAuthenticatorInput {
719 object_id: object.id(),
720 });
721 }
722 Owner::Shared { .. } => {
723 return Err(UserInputError::NotSharedObject);
726 }
727 };
728 }
729 InputObjectKind::SharedMoveObject {
730 id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
731 ..
732 } => {
733 return Err(UserInputError::InaccessibleSystemObject {
734 object_id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
735 });
736 }
737 InputObjectKind::SharedMoveObject {
738 id, mutable: true, ..
739 } => {
740 return Err(UserInputError::MutableSharedIsInMoveAuthenticatorInput {
741 object_id: id,
742 });
743 }
744 InputObjectKind::SharedMoveObject {
745 initial_shared_version: input_initial_shared_version,
746 ..
747 } => {
748 fp_ensure!(
749 object.version() < SequenceNumber::MAX_VALID_EXCL,
750 UserInputError::InvalidSequenceNumber
751 );
752
753 match object.owner {
754 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
755 return Err(UserInputError::NotSharedObject);
757 }
758 Owner::Shared {
759 initial_shared_version: actual_initial_shared_version,
760 } => {
761 fp_ensure!(
762 input_initial_shared_version == actual_initial_shared_version,
763 UserInputError::SharedObjectStartingVersionMismatch
764 )
765 }
766 }
767 }
768 };
769 Ok(())
770 }
771
772 fn checked_input_objects_union(
779 base_set: CheckedInputObjects,
780 other_set: &CheckedInputObjects,
781 ) -> IotaResult<CheckedInputObjects> {
782 let mut base_set = base_set.into_inner();
783 for other_object in other_set.inner().iter() {
784 if let Some(base_object) = base_set.find_object_id_mut(other_object.id()) {
785 assert_eq!(
787 base_object.object, other_object.object,
788 "The object read result for input objects with the same id must be equal"
789 );
790
791 if let ObjectReadResultKind::Object(_) = &other_object.object {
794 base_object
795 .input_object_kind
796 .left_union_with_checks(&other_object.input_object_kind)?;
797 }
798 } else {
799 base_set.push(other_object.clone());
800 }
801 }
802 Ok(base_set.into_checked())
803 }
804
805 #[instrument(level = "trace", skip_all)]
807 pub fn check_non_system_packages_to_be_published(
808 transaction: &TransactionData,
809 protocol_config: &ProtocolConfig,
810 metrics: &Arc<BytecodeVerifierMetrics>,
811 verifier_signing_config: &VerifierSigningConfig,
812 ) -> UserInputResult<()> {
813 if transaction.is_system_tx() {
815 return Ok(());
816 }
817
818 let TransactionKind::ProgrammableTransaction(pt) = transaction.kind() else {
819 return Ok(());
820 };
821
822 let signing_limits = Some(verifier_signing_config.limits_for_signing());
825 let mut verifier = iota_execution::verifier(protocol_config, signing_limits, metrics);
826 let mut meter = verifier.meter(verifier_signing_config.meter_config_for_signing());
827
828 let shared_meter_verifier_timer = metrics
830 .verifier_runtime_per_ptb_success_latency
831 .start_timer();
832
833 let verifier_status = pt
834 .non_system_packages_to_be_published()
835 .try_for_each(|module_bytes| {
836 verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
837 })
838 .map_err(|e| UserInputError::PackageVerificationTimedout { err: e.to_string() });
839
840 match verifier_status {
841 Ok(_) => {
842 shared_meter_verifier_timer.stop_and_record();
844 }
845 Err(err) => {
846 metrics
849 .verifier_runtime_per_ptb_timeout_latency
850 .observe(shared_meter_verifier_timer.stop_and_discard());
851 return Err(err);
852 }
853 };
854
855 Ok(())
856 }
857}