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 is_execute_transaction_to_effects: bool,
58 ) -> IotaResult<IotaGasStatus> {
59 if transaction.is_system_tx() {
60 Ok(IotaGasStatus::new_unmetered())
61 } else {
62 check_gas(
63 objects,
64 protocol_config,
65 reference_gas_price,
66 gas,
67 transaction.gas_price(),
68 transaction.gas_budget(),
69 authentication_gas_budget,
70 is_execute_transaction_to_effects,
71 )
72 }
73 }
74
75 #[instrument(level = "trace", skip_all, fields(tx_digest = ?transaction.digest()))]
76 pub fn check_transaction_input(
77 protocol_config: &ProtocolConfig,
78 reference_gas_price: u64,
79 transaction: &TransactionData,
80 input_objects: InputObjects,
81 receiving_objects: &ReceivingObjects,
82 metrics: &Arc<BytecodeVerifierMetrics>,
83 verifier_signing_config: &VerifierSigningConfig,
84 authentication_gas_budget: u64,
85 ) -> IotaResult<(IotaGasStatus, CheckedInputObjects)> {
86 let gas_status = check_transaction_input_inner(
87 protocol_config,
88 reference_gas_price,
89 transaction,
90 &input_objects,
91 &[],
92 authentication_gas_budget,
93 false,
94 )?;
95 check_receiving_objects(&input_objects, receiving_objects)?;
96 check_non_system_packages_to_be_published(
98 transaction,
99 protocol_config,
100 metrics,
101 verifier_signing_config,
102 )?;
103
104 Ok((gas_status, input_objects.into_checked()))
105 }
106
107 #[instrument(level = "trace", skip_all, fields(tx_digest = ?transaction.digest()))]
108 pub fn check_transaction_input_with_given_gas(
109 protocol_config: &ProtocolConfig,
110 reference_gas_price: u64,
111 transaction: &TransactionData,
112 mut input_objects: InputObjects,
113 receiving_objects: ReceivingObjects,
114 gas_object: Object,
115 metrics: &Arc<BytecodeVerifierMetrics>,
116 verifier_signing_config: &VerifierSigningConfig,
117 ) -> IotaResult<(IotaGasStatus, CheckedInputObjects)> {
118 let gas_object_ref = gas_object.compute_object_reference();
119 input_objects.push(ObjectReadResult::new_from_gas_object(&gas_object));
120
121 let gas_status = check_transaction_input_inner(
122 protocol_config,
123 reference_gas_price,
124 transaction,
125 &input_objects,
126 &[gas_object_ref],
127 0,
128 true,
129 )?;
130 check_receiving_objects(&input_objects, &receiving_objects)?;
131 check_non_system_packages_to_be_published(
133 transaction,
134 protocol_config,
135 metrics,
136 verifier_signing_config,
137 )?;
138
139 Ok((gas_status, input_objects.into_checked()))
140 }
141
142 #[instrument(level = "trace", skip_all)]
148 pub fn check_certificate_input(
149 cert: &VerifiedExecutableTransaction,
150 input_objects: InputObjects,
151 protocol_config: &ProtocolConfig,
152 reference_gas_price: u64,
153 ) -> IotaResult<(IotaGasStatus, CheckedInputObjects)> {
154 let transaction = cert.data().transaction_data();
155 let gas_status = check_transaction_input_inner(
156 protocol_config,
157 reference_gas_price,
158 transaction,
159 &input_objects,
160 &[],
161 0,
162 true,
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 true,
256 )?;
257
258 let authenticator_input_objects = authenticator_input_objects.into_checked();
260 let input_objects_union = checked_input_objects_union(
261 tx_input_objects.into_checked(),
262 &authenticator_input_objects,
263 )?;
264
265 Ok((gas_status, authenticator_input_objects, input_objects_union))
266 }
267
268 fn check_transaction_input_inner(
270 protocol_config: &ProtocolConfig,
271 reference_gas_price: u64,
272 transaction: &TransactionData,
273 input_objects: &InputObjects,
274 gas_override: &[ObjectRef],
276 authentication_gas_budget: u64,
277 is_execute_transaction_to_effects: bool,
278 ) -> IotaResult<IotaGasStatus> {
279 let gas = if gas_override.is_empty() {
281 transaction.gas()
282 } else {
283 gas_override
284 };
285
286 let gas_status = get_gas_status(
287 input_objects,
288 gas,
289 protocol_config,
290 reference_gas_price,
291 transaction,
292 authentication_gas_budget,
293 is_execute_transaction_to_effects,
294 )?;
295 check_objects(transaction, input_objects)?;
296
297 Ok(gas_status)
298 }
299
300 #[instrument(level = "trace", skip_all)]
301 fn check_receiving_objects(
302 input_objects: &InputObjects,
303 receiving_objects: &ReceivingObjects,
304 ) -> Result<(), IotaError> {
305 let mut objects_in_txn: HashSet<_> = input_objects
306 .object_kinds()
307 .map(|x| x.object_id())
308 .collect();
309
310 for ReceivingObjectReadResult {
319 object_ref: (object_id, version, object_digest),
320 object,
321 } in receiving_objects.iter()
322 {
323 fp_ensure!(
324 *version < SequenceNumber::MAX_VALID_EXCL,
325 UserInputError::InvalidSequenceNumber.into()
326 );
327
328 let Some(object) = object.as_object() else {
329 continue;
331 };
332
333 if !(object.owner.is_address_owned()
334 && object.version() == *version
335 && object.digest() == *object_digest)
336 {
337 fp_ensure!(
339 object.version() == *version,
340 UserInputError::ObjectVersionUnavailableForConsumption {
341 provided_obj_ref: (*object_id, *version, *object_digest),
342 current_version: object.version(),
343 }
344 .into()
345 );
346
347 fp_ensure!(
349 !object.is_package(),
350 UserInputError::MovePackageAsObject {
351 object_id: *object_id
352 }
353 .into()
354 );
355
356 let expected_digest = object.digest();
358 fp_ensure!(
359 expected_digest == *object_digest,
360 UserInputError::InvalidObjectDigest {
361 object_id: *object_id,
362 expected_digest
363 }
364 .into()
365 );
366
367 match object.owner {
368 Owner::AddressOwner(_) => {
369 debug_assert!(
370 false,
371 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
372 (*object_id, *version, *object_id),
373 object
374 );
375 error!(
376 "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
377 (*object_id, *version, *object_id),
378 object
379 );
380 fp_bail!(
383 UserInputError::ObjectNotFound {
384 object_id: *object_id,
385 version: Some(*version),
386 }
387 .into()
388 )
389 }
390 Owner::ObjectOwner(owner) => {
391 fp_bail!(
392 UserInputError::InvalidChildObjectArgument {
393 child_id: object.id(),
394 parent_id: owner.into(),
395 }
396 .into()
397 )
398 }
399 Owner::Shared { .. } => fp_bail!(UserInputError::NotSharedObject.into()),
400 Owner::Immutable => fp_bail!(
401 UserInputError::MutableParameterExpected {
402 object_id: *object_id
403 }
404 .into()
405 ),
406 };
407 }
408
409 fp_ensure!(
410 !objects_in_txn.contains(object_id),
411 UserInputError::DuplicateObjectRefInput.into()
412 );
413
414 objects_in_txn.insert(*object_id);
415 }
416 Ok(())
417 }
418
419 #[instrument(level = "trace", skip_all)]
422 fn check_gas(
423 objects: &InputObjects,
424 protocol_config: &ProtocolConfig,
425 reference_gas_price: u64,
426 gas: &[ObjectRef],
427 gas_price: u64,
428 transaction_gas_budget: u64,
429 authentication_gas_budget: u64,
430 is_execute_transaction_to_effects: bool,
431 ) -> IotaResult<IotaGasStatus> {
432 let gas_budget_to_set = if authentication_gas_budget > 0 {
433 let protocol_max_auth_gas =
436 protocol_config.max_auth_gas_as_option().ok_or_else(|| {
437 UserInputError::Unsupported(
438 "Transaction requires authentication gas but max_auth_gas is not enabled"
439 .to_string(),
440 )
441 })?;
442
443 if is_execute_transaction_to_effects {
450 transaction_gas_budget
451 } else {
452 authentication_gas_budget.min(protocol_max_auth_gas)
453 }
454 } else {
455 transaction_gas_budget
458 };
459
460 let gas_budget_to_check = transaction_gas_budget;
463
464 let gas_status = IotaGasStatus::new(
465 gas_budget_to_set,
466 gas_price,
467 reference_gas_price,
468 protocol_config,
469 )?;
470
471 let objects: BTreeMap<_, _> = objects.iter().map(|o| (o.id(), o)).collect();
474 let mut gas_objects = vec![];
475 for obj_ref in gas {
476 let obj = objects.get(&obj_ref.0);
477 let obj = *obj.ok_or(UserInputError::ObjectNotFound {
478 object_id: obj_ref.0,
479 version: Some(obj_ref.1),
480 })?;
481 gas_objects.push(obj);
482 }
483 gas_status.check_gas_balance(&gas_objects, gas_budget_to_check)?;
484 Ok(gas_status)
485 }
486
487 #[instrument(level = "trace", skip_all)]
490 fn check_objects(transaction: &TransactionData, objects: &InputObjects) -> UserInputResult<()> {
491 let mut used_objects: HashSet<IotaAddress> = HashSet::new();
493 for object in objects.iter() {
494 if object.is_mutable() {
495 fp_ensure!(
496 used_objects.insert(object.id().into()),
497 UserInputError::MutableObjectUsedMoreThanOnce {
498 object_id: object.id()
499 }
500 );
501 }
502 }
503
504 if !transaction.is_genesis_tx() && objects.is_empty() {
505 return Err(UserInputError::ObjectInputArityViolation);
506 }
507
508 let gas_coins: HashSet<ObjectID> =
509 HashSet::from_iter(transaction.gas().iter().map(|obj_ref| obj_ref.0));
510 for object in objects.iter() {
511 let input_object_kind = object.input_object_kind;
512
513 match &object.object {
514 ObjectReadResultKind::Object(object) => {
515 let owner_address = if gas_coins.contains(&object.id()) {
517 transaction.gas_owner()
518 } else {
519 transaction.sender()
520 };
521 let system_transaction = transaction.is_system_tx();
524 check_one_object(
525 &owner_address,
526 input_object_kind,
527 object,
528 system_transaction,
529 )?;
530 }
531 ObjectReadResultKind::DeletedSharedObject(_, _) => (),
533 ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
536 }
537 }
538
539 Ok(())
540 }
541
542 fn check_one_object(
544 owner: &IotaAddress,
545 object_kind: InputObjectKind,
546 object: &Object,
547 system_transaction: bool,
548 ) -> UserInputResult {
549 match object_kind {
550 InputObjectKind::MovePackage(package_id) => {
551 fp_ensure!(
552 object.data.try_as_package().is_some(),
553 UserInputError::MoveObjectAsPackage {
554 object_id: package_id
555 }
556 );
557 }
558 InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
559 fp_ensure!(
560 !object.is_package(),
561 UserInputError::MovePackageAsObject { object_id }
562 );
563 fp_ensure!(
564 sequence_number < SequenceNumber::MAX_VALID_EXCL,
565 UserInputError::InvalidSequenceNumber
566 );
567
568 assert_eq!(
570 object.version(),
571 sequence_number,
572 "The fetched object version {} does not match the requested version {}, object id: {}",
573 object.version(),
574 sequence_number,
575 object.id(),
576 );
577
578 let expected_digest = object.digest();
580 fp_ensure!(
581 expected_digest == object_digest,
582 UserInputError::InvalidObjectDigest {
583 object_id,
584 expected_digest
585 }
586 );
587
588 match object.owner {
589 Owner::Immutable => {
590 }
592 Owner::AddressOwner(actual_owner) => {
593 fp_ensure!(
595 owner == &actual_owner,
596 UserInputError::IncorrectUserSignature {
597 error: format!(
598 "Object {object_id:?} is owned by account address {actual_owner}, but given owner/signer address is {owner}"
599 ),
600 }
601 );
602 }
603 Owner::ObjectOwner(owner) => {
604 return Err(UserInputError::InvalidChildObjectArgument {
605 child_id: object.id(),
606 parent_id: owner.into(),
607 });
608 }
609 Owner::Shared { .. } => {
610 return Err(UserInputError::NotSharedObject);
613 }
614 };
615 }
616 InputObjectKind::SharedMoveObject {
617 id: IOTA_CLOCK_OBJECT_ID,
618 initial_shared_version: IOTA_CLOCK_OBJECT_SHARED_VERSION,
619 mutable: true,
620 } => {
621 if system_transaction {
624 return Ok(());
625 } else {
626 return Err(UserInputError::ImmutableParameterExpected {
627 object_id: IOTA_CLOCK_OBJECT_ID,
628 });
629 }
630 }
631 InputObjectKind::SharedMoveObject {
632 id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
633 ..
634 } => {
635 if system_transaction {
636 return Ok(());
637 } else {
638 return Err(UserInputError::InaccessibleSystemObject {
639 object_id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
640 });
641 }
642 }
643 InputObjectKind::SharedMoveObject {
644 id: IOTA_RANDOMNESS_STATE_OBJECT_ID,
645 mutable: true,
646 ..
647 } => {
648 if system_transaction {
651 return Ok(());
652 } else {
653 return Err(UserInputError::ImmutableParameterExpected {
654 object_id: IOTA_RANDOMNESS_STATE_OBJECT_ID,
655 });
656 }
657 }
658 InputObjectKind::SharedMoveObject {
659 initial_shared_version: input_initial_shared_version,
660 ..
661 } => {
662 fp_ensure!(
663 object.version() < SequenceNumber::MAX_VALID_EXCL,
664 UserInputError::InvalidSequenceNumber
665 );
666
667 match object.owner {
668 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
669 return Err(UserInputError::NotSharedObject);
671 }
672 Owner::Shared {
673 initial_shared_version: actual_initial_shared_version,
674 } => {
675 fp_ensure!(
676 input_initial_shared_version == actual_initial_shared_version,
677 UserInputError::SharedObjectStartingVersionMismatch
678 )
679 }
680 }
681 }
682 };
683 Ok(())
684 }
685
686 #[instrument(level = "trace", skip_all)]
689 fn check_move_authenticator_objects(
690 authenticator_objects: &InputObjects,
691 ) -> UserInputResult<()> {
692 for object in authenticator_objects.iter() {
693 let input_object_kind = object.input_object_kind;
694
695 match &object.object {
696 ObjectReadResultKind::Object(object) => {
697 check_one_move_authenticator_object(input_object_kind, object)?;
698 }
699 ObjectReadResultKind::DeletedSharedObject(_, _) => (),
701 ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
704 }
705 }
706
707 Ok(())
708 }
709
710 fn check_one_move_authenticator_object(
712 object_kind: InputObjectKind,
713 object: &Object,
714 ) -> UserInputResult {
715 match object_kind {
716 InputObjectKind::MovePackage(package_id) => {
717 return Err(UserInputError::PackageIsInMoveAuthenticatorInput { package_id });
718 }
719 InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
720 fp_ensure!(
721 !object.is_package(),
722 UserInputError::MovePackageAsObject { object_id }
723 );
724 fp_ensure!(
725 sequence_number < SequenceNumber::MAX_VALID_EXCL,
726 UserInputError::InvalidSequenceNumber
727 );
728
729 assert_eq!(
731 object.version(),
732 sequence_number,
733 "The fetched object version {} does not match the requested version {}, object id: {}",
734 object.version(),
735 sequence_number,
736 object.id(),
737 );
738
739 let expected_digest = object.digest();
741 fp_ensure!(
742 expected_digest == object_digest,
743 UserInputError::InvalidObjectDigest {
744 object_id,
745 expected_digest
746 }
747 );
748
749 match object.owner {
750 Owner::Immutable => {
751 }
753 Owner::AddressOwner { .. } => {
754 return Err(UserInputError::AddressOwnedIsInMoveAuthenticatorInput {
755 object_id: object.id(),
756 });
757 }
758 Owner::ObjectOwner { .. } => {
759 return Err(UserInputError::ObjectOwnedIsInMoveAuthenticatorInput {
760 object_id: object.id(),
761 });
762 }
763 Owner::Shared { .. } => {
764 return Err(UserInputError::NotSharedObject);
767 }
768 };
769 }
770 InputObjectKind::SharedMoveObject {
771 id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
772 ..
773 } => {
774 return Err(UserInputError::InaccessibleSystemObject {
775 object_id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
776 });
777 }
778 InputObjectKind::SharedMoveObject {
779 id, mutable: true, ..
780 } => {
781 return Err(UserInputError::MutableSharedIsInMoveAuthenticatorInput {
782 object_id: id,
783 });
784 }
785 InputObjectKind::SharedMoveObject {
786 initial_shared_version: input_initial_shared_version,
787 ..
788 } => {
789 fp_ensure!(
790 object.version() < SequenceNumber::MAX_VALID_EXCL,
791 UserInputError::InvalidSequenceNumber
792 );
793
794 match object.owner {
795 Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
796 return Err(UserInputError::NotSharedObject);
798 }
799 Owner::Shared {
800 initial_shared_version: actual_initial_shared_version,
801 } => {
802 fp_ensure!(
803 input_initial_shared_version == actual_initial_shared_version,
804 UserInputError::SharedObjectStartingVersionMismatch
805 )
806 }
807 }
808 }
809 };
810 Ok(())
811 }
812
813 fn checked_input_objects_union(
820 base_set: CheckedInputObjects,
821 other_set: &CheckedInputObjects,
822 ) -> IotaResult<CheckedInputObjects> {
823 let mut base_set = base_set.into_inner();
824 for other_object in other_set.inner().iter() {
825 if let Some(base_object) = base_set.find_object_id_mut(other_object.id()) {
826 assert_eq!(
828 base_object.object, other_object.object,
829 "The object read result for input objects with the same id must be equal"
830 );
831
832 if let ObjectReadResultKind::Object(_) = &other_object.object {
835 base_object
836 .input_object_kind
837 .left_union_with_checks(&other_object.input_object_kind)?;
838 }
839 } else {
840 base_set.push(other_object.clone());
841 }
842 }
843 Ok(base_set.into_checked())
844 }
845
846 #[instrument(level = "trace", skip_all)]
848 pub fn check_non_system_packages_to_be_published(
849 transaction: &TransactionData,
850 protocol_config: &ProtocolConfig,
851 metrics: &Arc<BytecodeVerifierMetrics>,
852 verifier_signing_config: &VerifierSigningConfig,
853 ) -> UserInputResult<()> {
854 if transaction.is_system_tx() {
856 return Ok(());
857 }
858
859 let TransactionKind::ProgrammableTransaction(pt) = transaction.kind() else {
860 return Ok(());
861 };
862
863 let signing_limits = Some(verifier_signing_config.limits_for_signing());
866 let mut verifier = iota_execution::verifier(protocol_config, signing_limits, metrics);
867 let mut meter = verifier.meter(verifier_signing_config.meter_config_for_signing());
868
869 let shared_meter_verifier_timer = metrics
871 .verifier_runtime_per_ptb_success_latency
872 .start_timer();
873
874 let verifier_status = pt
875 .non_system_packages_to_be_published()
876 .try_for_each(|module_bytes| {
877 verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
878 })
879 .map_err(|e| UserInputError::PackageVerificationTimedout { err: e.to_string() });
880
881 match verifier_status {
882 Ok(_) => {
883 shared_meter_verifier_timer.stop_and_record();
885 }
886 Err(err) => {
887 metrics
890 .verifier_runtime_per_ptb_timeout_latency
891 .observe(shared_meter_verifier_timer.stop_and_discard());
892 return Err(err);
893 }
894 };
895
896 Ok(())
897 }
898}