iota_transaction_checks/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5pub 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_SHARED_VERSION,
20        base_types::{IotaAddress, ObjectID, ObjectRef, SequenceNumber},
21        error::{IotaError, IotaResult, UserInputError, UserInputResult},
22        executable_transaction::VerifiedExecutableTransaction,
23        fp_bail, fp_ensure,
24        gas::IotaGasStatus,
25        metrics::BytecodeVerifierMetrics,
26        object::{Object, Owner},
27        transaction::{
28            CheckedInputObjects, InputObjectKind, InputObjects, ObjectReadResult,
29            ObjectReadResultKind, ProgrammableTransactionExt, ReceivingObjectReadResult,
30            ReceivingObjects, TransactionData, TransactionDataAPI, TransactionKind,
31            TransactionKindExt,
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    // Entry point for all checks related to gas.
47    // Called on both signing and execution.
48    // On success the gas part of the transaction (gas data and gas coins)
49    // is verified and good to go
50    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        // Runs verifier, which could be expensive.
97        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        // Runs verifier, which could be expensive.
132        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    // Since the purpose of this function is to audit certified transactions,
143    // the checks here should be a strict subset of the checks in
144    // check_transaction_input(). For checks not performed in this function but
145    // in check_transaction_input(), we should add a comment calling out the
146    // difference.
147    #[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        // NB: We do not check receiving objects when executing. Only at signing
165        // time do we check. NB: move verifier is only checked at
166        // signing time, not at execution.
167
168        Ok((gas_status, input_objects.into_checked()))
169    }
170
171    /// WARNING! This should only be used for the dev-inspect transaction. This
172    /// transaction type bypasses many of the normal object checks
173    #[instrument(level = "trace", skip_all)]
174    pub fn check_dev_inspect_input(
175        config: &ProtocolConfig,
176        kind: &TransactionKind,
177        input_objects: InputObjects,
178        // TODO: check ReceivingObjects for dev inspect?
179        _receiving_objects: ReceivingObjects,
180    ) -> IotaResult<CheckedInputObjects> {
181        kind.validity_check(config)?;
182        if kind.is_system() {
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                // object was deleted
192                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    /// A common function to check the `MoveAuthenticator` inputs for signing.
210    ///
211    /// Checks that the authenticator inputs meet the requirements and returns
212    /// checked authenticator input objects, among which we also find the
213    /// account object.
214    #[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    /// A function to aggregate the checked authenticator input objects for
224    /// multiple `MoveAuthenticators` into one `CheckedInputObjects` to be used
225    /// for execution.
226    pub fn aggregate_authenticator_input_objects(
227        per_authenticator_checked_input_objects: &[&CheckedInputObjects],
228    ) -> IotaResult<CheckedInputObjects> {
229        let mut aggregated_authenticator_input_objects =
230            CheckedInputObjects::new_with_checked_transaction_inputs(InputObjects::new(vec![]));
231
232        for authenticator_checked_input_objects in per_authenticator_checked_input_objects.iter() {
233            aggregated_authenticator_input_objects = checked_input_objects_union(
234                aggregated_authenticator_input_objects,
235                authenticator_checked_input_objects,
236            )?;
237        }
238
239        Ok(aggregated_authenticator_input_objects)
240    }
241
242    /// A function to check the `MoveAuthenticator` inputs for execution and
243    /// then for certificate execution.
244    /// To be used instead of check_certificate_input when there is a Move
245    /// authenticator present.
246    ///
247    /// Checks that there is enough gas to pay for the authenticator and
248    /// transaction execution in the transaction inputs. And that the
249    /// authenticator inputs meet the requirements.
250    /// It returns the gas status, the checked authenticator input objects, and
251    /// the union of the checked authenticator input objects and transaction
252    /// input objects.
253    #[instrument(level = "trace", skip_all)]
254    pub fn check_certificate_and_move_authenticator_input(
255        cert: &VerifiedExecutableTransaction,
256        tx_input_objects: InputObjects,
257        per_authenticator_input_objects: Vec<InputObjects>,
258        authenticator_gas_budget: u64,
259        protocol_config: &ProtocolConfig,
260        reference_gas_price: u64,
261    ) -> IotaResult<(IotaGasStatus, Vec<CheckedInputObjects>, CheckedInputObjects)> {
262        // Check Move authenticator inputs first
263        per_authenticator_input_objects
264            .iter()
265            .try_for_each(check_move_authenticator_objects)?;
266
267        // Check certificate inputs next
268        let transaction = cert.data().transaction_data();
269        let gas_status = check_transaction_input_inner(
270            protocol_config,
271            reference_gas_price,
272            transaction,
273            &tx_input_objects,
274            &[],
275            authenticator_gas_budget,
276            true,
277        )?;
278
279        let per_authenticator_checked_input_objects = per_authenticator_input_objects
280            .into_iter()
281            .map(|objects| objects.into_checked())
282            .collect::<Vec<_>>();
283
284        // Create a checked union of input objects
285        let mut input_objects_union = tx_input_objects.into_checked();
286        for objects in per_authenticator_checked_input_objects.iter() {
287            input_objects_union = checked_input_objects_union(input_objects_union, objects)?;
288        }
289
290        Ok((
291            gas_status,
292            per_authenticator_checked_input_objects,
293            input_objects_union,
294        ))
295    }
296
297    // Common checks performed for transactions and certificates.
298    fn check_transaction_input_inner(
299        protocol_config: &ProtocolConfig,
300        reference_gas_price: u64,
301        transaction: &TransactionData,
302        input_objects: &InputObjects,
303        // Overrides the gas objects in the transaction.
304        gas_override: &[ObjectRef],
305        authentication_gas_budget: u64,
306        is_execute_transaction_to_effects: bool,
307    ) -> IotaResult<IotaGasStatus> {
308        // Cheap validity checks that is ok to run multiple times during processing.
309        let gas = if gas_override.is_empty() {
310            transaction.gas()
311        } else {
312            gas_override
313        };
314
315        let gas_status = get_gas_status(
316            input_objects,
317            gas,
318            protocol_config,
319            reference_gas_price,
320            transaction,
321            authentication_gas_budget,
322            is_execute_transaction_to_effects,
323        )?;
324        check_objects(transaction, input_objects)?;
325
326        Ok(gas_status)
327    }
328
329    #[instrument(level = "trace", skip_all)]
330    fn check_receiving_objects(
331        input_objects: &InputObjects,
332        receiving_objects: &ReceivingObjects,
333    ) -> Result<(), IotaError> {
334        let mut objects_in_txn: HashSet<_> = input_objects
335            .object_kinds()
336            .map(|x| x.object_id())
337            .collect();
338
339        // Since we're at signing we check that every object reference that we are
340        // receiving is the most recent version of that object. If it's been
341        // received at the version specified we let it through to allow the
342        // transaction to run and fail to unlock any other objects in
343        // the transaction. Otherwise, we return an error.
344        //
345        // If there are any object IDs in common (either between receiving objects and
346        // input objects) we return an error.
347        for ReceivingObjectReadResult { object_ref, object } in receiving_objects.iter() {
348            fp_ensure!(
349                object_ref.version < SequenceNumber::MAX_VALID_EXCL,
350                UserInputError::InvalidSequenceNumber.into()
351            );
352
353            let Some(object) = object.as_object() else {
354                // object was previously received
355                continue;
356            };
357
358            if !(object.owner.is_address()
359                && object.version() == object_ref.version
360                && object.digest() == object_ref.digest)
361            {
362                // Version mismatch
363                fp_ensure!(
364                    object.version() == object_ref.version,
365                    UserInputError::ObjectVersionUnavailableForConsumption {
366                        provided_obj_ref: *object_ref,
367                        current_version: object.version(),
368                    }
369                    .into()
370                );
371
372                // Tried to receive a package
373                fp_ensure!(
374                    !object.is_package(),
375                    UserInputError::MovePackageAsObject {
376                        object_id: object_ref.object_id
377                    }
378                    .into()
379                );
380
381                // Digest mismatch
382                let expected_digest = object.digest();
383                fp_ensure!(
384                    expected_digest == object_ref.digest,
385                    UserInputError::InvalidObjectDigest {
386                        object_id: object_ref.object_id,
387                        expected_digest
388                    }
389                    .into()
390                );
391
392                match object.owner {
393                    Owner::Address(_) => {
394                        debug_assert!(
395                            false,
396                            "Receiving object {object_ref:?} is invalid but we expect it should be valid. {object:?}"
397                        );
398                        error!(
399                            "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
400                            object_ref, object
401                        );
402                        // We should never get here, but if for some reason we do just default to
403                        // object not found and reject signing the transaction.
404                        fp_bail!(
405                            UserInputError::ObjectNotFound {
406                                object_id: object_ref.object_id,
407                                version: Some(object_ref.version),
408                            }
409                            .into()
410                        )
411                    }
412                    Owner::Object(owner) => {
413                        fp_bail!(
414                            UserInputError::InvalidChildObjectArgument {
415                                child_id: object.id(),
416                                parent_id: owner,
417                            }
418                            .into()
419                        )
420                    }
421                    Owner::Shared(_) => fp_bail!(UserInputError::NotSharedObject.into()),
422                    Owner::Immutable => fp_bail!(
423                        UserInputError::MutableParameterExpected {
424                            object_id: object_ref.object_id
425                        }
426                        .into()
427                    ),
428                    _ => {
429                        unimplemented!("a new Owner enum variant was added and needs to be handled")
430                    }
431                };
432            }
433
434            fp_ensure!(
435                !objects_in_txn.contains(&object_ref.object_id),
436                UserInputError::DuplicateObjectRefInput.into()
437            );
438
439            objects_in_txn.insert(object_ref.object_id);
440        }
441        Ok(())
442    }
443
444    /// Check transaction gas data/info and gas coins consistency.
445    /// Return the gas status to be used for the lifecycle of the transaction.
446    #[instrument(level = "trace", skip_all)]
447    fn check_gas(
448        objects: &InputObjects,
449        protocol_config: &ProtocolConfig,
450        reference_gas_price: u64,
451        gas: &[ObjectRef],
452        gas_price: u64,
453        transaction_gas_budget: u64,
454        authentication_gas_budget: u64,
455        is_execute_transaction_to_effects: bool,
456    ) -> IotaResult<IotaGasStatus> {
457        let gas_budget_to_set = if authentication_gas_budget > 0 {
458            // If there is an authentication gas budget, then we are checking if
459            // max_gas_budget is Some. If not, that is UserInputError.
460            let protocol_max_auth_gas =
461                protocol_config.max_auth_gas_as_option().ok_or_else(|| {
462                    UserInputError::Unsupported(
463                        "Transaction requires authentication gas but max_auth_gas is not enabled"
464                            .to_string(),
465                    )
466                })?;
467
468            // Execution phase:
469            //  - meter transaction + authentication;
470            //  - it needs the full budget.
471            // Signing phase:
472            //  - meter only authentication;
473            //  - it only needs authentication budget.
474            if is_execute_transaction_to_effects {
475                transaction_gas_budget
476            } else {
477                authentication_gas_budget.min(protocol_max_auth_gas)
478            }
479        } else {
480            // If there is no authentication gas budget, then we are only checking the
481            // transaction gas budget.
482            transaction_gas_budget
483        };
484
485        // Budget to check is always the one set by the user (which should cover full
486        // transaction + authentication costs).
487        let gas_budget_to_check = transaction_gas_budget;
488
489        let gas_status = IotaGasStatus::new(
490            gas_budget_to_set,
491            gas_price,
492            reference_gas_price,
493            protocol_config,
494        )?;
495
496        // Check balance and coins consistency
497        // Load all gas coins
498        let objects: BTreeMap<_, _> = objects.iter().map(|o| (o.id(), o)).collect();
499        let mut gas_objects = vec![];
500        for obj_ref in gas {
501            let obj = objects.get(&obj_ref.object_id);
502            let obj = *obj.ok_or(UserInputError::ObjectNotFound {
503                object_id: obj_ref.object_id,
504                version: Some(obj_ref.version),
505            })?;
506            gas_objects.push(obj);
507        }
508        gas_status.check_gas_balance(&gas_objects, gas_budget_to_check)?;
509        Ok(gas_status)
510    }
511
512    /// Check all the objects used in the transaction against the database, and
513    /// ensure that they are all the correct version and number.
514    #[instrument(level = "trace", skip_all)]
515    fn check_objects(transaction: &TransactionData, objects: &InputObjects) -> UserInputResult<()> {
516        // We require that mutable objects cannot show up more than once.
517        let mut used_objects: HashSet<IotaAddress> = HashSet::new();
518        for object in objects.iter() {
519            if object.is_mutable() {
520                fp_ensure!(
521                    used_objects.insert(object.id().into()),
522                    UserInputError::MutableObjectUsedMoreThanOnce {
523                        object_id: object.id()
524                    }
525                );
526            }
527        }
528
529        if !transaction.is_genesis_tx() && objects.is_empty() {
530            return Err(UserInputError::ObjectInputArityViolation);
531        }
532
533        let gas_coins: HashSet<ObjectID> =
534            HashSet::from_iter(transaction.gas().iter().map(|obj_ref| obj_ref.object_id));
535        for object in objects.iter() {
536            let input_object_kind = object.input_object_kind;
537
538            match &object.object {
539                ObjectReadResultKind::Object(object) => {
540                    // For Gas Object, we check the object is owned by gas owner
541                    let owner_address = if gas_coins.contains(&object.id()) {
542                        transaction.gas_owner()
543                    } else {
544                        transaction.sender()
545                    };
546                    // Check if the object contents match the type of lock we need for
547                    // this object.
548                    let system_transaction = transaction.is_system_tx();
549                    check_one_object(
550                        &owner_address,
551                        input_object_kind,
552                        object,
553                        system_transaction,
554                    )?;
555                }
556                // We skip checking a deleted shared object because it no longer exists
557                ObjectReadResultKind::DeletedSharedObject(_, _) => (),
558                // We skip checking shared objects from cancelled transactions since we are not
559                // reading it.
560                ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
561            }
562        }
563
564        Ok(())
565    }
566
567    /// Check one object against a reference
568    fn check_one_object(
569        owner: &IotaAddress,
570        object_kind: InputObjectKind,
571        object: &Object,
572        system_transaction: bool,
573    ) -> UserInputResult {
574        match object_kind {
575            InputObjectKind::MovePackage(package_id) => {
576                fp_ensure!(
577                    object.data.as_package_opt().is_some(),
578                    UserInputError::MoveObjectAsPackage {
579                        object_id: package_id
580                    }
581                );
582            }
583            InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
584                fp_ensure!(
585                    !object.is_package(),
586                    UserInputError::MovePackageAsObject {
587                        object_id: object_ref.object_id
588                    }
589                );
590                fp_ensure!(
591                    object_ref.version < SequenceNumber::MAX_VALID_EXCL,
592                    UserInputError::InvalidSequenceNumber
593                );
594
595                // This is an invariant - we just load the object with the given ID and version.
596                assert_eq!(
597                    object.version(),
598                    object_ref.version,
599                    "The fetched object version {} does not match the requested version {}, object id: {}",
600                    object.version(),
601                    object_ref.version,
602                    object.id(),
603                );
604
605                // Check the digest matches - user could give a mismatched ObjectDigest
606                let expected_digest = object.digest();
607                fp_ensure!(
608                    expected_digest == object_ref.digest,
609                    UserInputError::InvalidObjectDigest {
610                        object_id: object_ref.object_id,
611                        expected_digest
612                    }
613                );
614
615                match object.owner {
616                    Owner::Immutable => {
617                        // Nothing else to check for Immutable.
618                    }
619                    Owner::Address(actual_owner) => {
620                        // Check the owner is correct.
621                        fp_ensure!(
622                            owner == &actual_owner,
623                            UserInputError::IncorrectUserSignature {
624                                error: format!(
625                                    "Object {} is owned by account address {}, but given owner/signer address is {}",
626                                    object_ref.object_id, actual_owner, owner
627                                ),
628                            }
629                        );
630                    }
631                    Owner::Object(owner) => {
632                        return Err(UserInputError::InvalidChildObjectArgument {
633                            child_id: object.id(),
634                            parent_id: owner,
635                        });
636                    }
637                    Owner::Shared(_) => {
638                        // This object is a mutable shared object. However the transaction
639                        // specifies it as an owned object. This is inconsistent.
640                        return Err(UserInputError::NotSharedObject);
641                    }
642                    _ => {
643                        unimplemented!("a new Owner enum variant was added and needs to be handled")
644                    }
645                };
646            }
647            InputObjectKind::SharedMoveObject {
648                id: ObjectID::CLOCK,
649                initial_shared_version: IOTA_CLOCK_OBJECT_SHARED_VERSION,
650                mutable: true,
651            } => {
652                // Only system transactions can accept the Clock
653                // object as a mutable parameter.
654                if system_transaction {
655                    return Ok(());
656                } else {
657                    return Err(UserInputError::ImmutableParameterExpected {
658                        object_id: ObjectID::CLOCK,
659                    });
660                }
661            }
662            InputObjectKind::SharedMoveObject {
663                id: ObjectID::AUTHENTICATOR_STATE,
664                ..
665            } => {
666                if system_transaction {
667                    return Ok(());
668                } else {
669                    return Err(UserInputError::InaccessibleSystemObject {
670                        object_id: ObjectID::AUTHENTICATOR_STATE,
671                    });
672                }
673            }
674            InputObjectKind::SharedMoveObject {
675                id: ObjectID::RANDOMNESS_STATE,
676                mutable: true,
677                ..
678            } => {
679                // Only system transactions can accept the Random
680                // object as a mutable parameter.
681                if system_transaction {
682                    return Ok(());
683                } else {
684                    return Err(UserInputError::ImmutableParameterExpected {
685                        object_id: ObjectID::RANDOMNESS_STATE,
686                    });
687                }
688            }
689            InputObjectKind::SharedMoveObject {
690                initial_shared_version: input_initial_shared_version,
691                ..
692            } => {
693                fp_ensure!(
694                    object.version() < SequenceNumber::MAX_VALID_EXCL,
695                    UserInputError::InvalidSequenceNumber
696                );
697
698                match object.owner {
699                    Owner::Address(_) | Owner::Object(_) | Owner::Immutable => {
700                        // When someone locks an object as shared it must be shared already.
701                        return Err(UserInputError::NotSharedObject);
702                    }
703                    Owner::Shared(actual_initial_shared_version) => {
704                        fp_ensure!(
705                            input_initial_shared_version == actual_initial_shared_version,
706                            UserInputError::SharedObjectStartingVersionMismatch
707                        )
708                    }
709                    _ => {
710                        unimplemented!("a new Owner enum variant was added and needs to be handled")
711                    }
712                }
713            }
714        };
715        Ok(())
716    }
717
718    /// Check all the `MoveAuthenticator` related input objects against the
719    /// database.
720    #[instrument(level = "trace", skip_all)]
721    fn check_move_authenticator_objects(
722        authenticator_objects: &InputObjects,
723    ) -> UserInputResult<()> {
724        for object in authenticator_objects.iter() {
725            let input_object_kind = object.input_object_kind;
726
727            match &object.object {
728                ObjectReadResultKind::Object(object) => {
729                    check_one_move_authenticator_object(input_object_kind, object)?;
730                }
731                // We skip checking a deleted shared object because it no longer exists.
732                ObjectReadResultKind::DeletedSharedObject(_, _) => (),
733                // We skip checking shared objects from cancelled transactions since we are not
734                // reading it.
735                ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
736            }
737        }
738
739        Ok(())
740    }
741
742    /// Check one `MoveAuthenticator` input object.
743    fn check_one_move_authenticator_object(
744        object_kind: InputObjectKind,
745        object: &Object,
746    ) -> UserInputResult {
747        match object_kind {
748            InputObjectKind::MovePackage(package_id) => {
749                return Err(UserInputError::PackageIsInMoveAuthenticatorInput { package_id });
750            }
751            InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
752                fp_ensure!(
753                    !object.is_package(),
754                    UserInputError::MovePackageAsObject {
755                        object_id: object_ref.object_id
756                    }
757                );
758                fp_ensure!(
759                    object_ref.version < SequenceNumber::MAX_VALID_EXCL,
760                    UserInputError::InvalidSequenceNumber
761                );
762
763                // This is an invariant - we just load the object with the given ID and version.
764                assert_eq!(
765                    object.version(),
766                    object_ref.version,
767                    "The fetched object version {} does not match the requested version {}, object id: {}",
768                    object.version(),
769                    object_ref.version,
770                    object.id(),
771                );
772
773                // Check the digest matches - user could give a mismatched `ObjectDigest`.
774                let expected_digest = object.digest();
775                fp_ensure!(
776                    expected_digest == object_ref.digest,
777                    UserInputError::InvalidObjectDigest {
778                        object_id: object_ref.object_id,
779                        expected_digest
780                    }
781                );
782
783                match object.owner {
784                    Owner::Immutable => {
785                        // Nothing else to check for Immutable.
786                    }
787                    Owner::Address(_) => {
788                        return Err(UserInputError::AddressOwnedIsInMoveAuthenticatorInput {
789                            object_id: object.id(),
790                        });
791                    }
792                    Owner::Object(_) => {
793                        return Err(UserInputError::ObjectOwnedIsInMoveAuthenticatorInput {
794                            object_id: object.id(),
795                        });
796                    }
797                    Owner::Shared(_) => {
798                        // This object is a mutable shared object. However the transaction
799                        // specifies it as an owned object. This is inconsistent.
800                        return Err(UserInputError::NotSharedObject);
801                    }
802                    _ => {
803                        unimplemented!("a new Owner enum variant was added and needs to be handled")
804                    }
805                };
806            }
807            InputObjectKind::SharedMoveObject {
808                id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
809                ..
810            } => {
811                return Err(UserInputError::InaccessibleSystemObject {
812                    object_id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
813                });
814            }
815            InputObjectKind::SharedMoveObject {
816                id, mutable: true, ..
817            } => {
818                return Err(UserInputError::MutableSharedIsInMoveAuthenticatorInput {
819                    object_id: id,
820                });
821            }
822            InputObjectKind::SharedMoveObject {
823                initial_shared_version: input_initial_shared_version,
824                ..
825            } => {
826                fp_ensure!(
827                    object.version() < SequenceNumber::MAX_VALID_EXCL,
828                    UserInputError::InvalidSequenceNumber
829                );
830
831                match object.owner {
832                    Owner::Address(_) | Owner::Object(_) | Owner::Immutable => {
833                        // When someone locks an object as shared it must be shared already.
834                        return Err(UserInputError::NotSharedObject);
835                    }
836                    Owner::Shared(actual_initial_shared_version) => {
837                        fp_ensure!(
838                            input_initial_shared_version == actual_initial_shared_version,
839                            UserInputError::SharedObjectStartingVersionMismatch
840                        )
841                    }
842                    _ => {
843                        unimplemented!("a new Owner enum variant was added and needs to be handled")
844                    }
845                }
846            }
847        };
848        Ok(())
849    }
850
851    /// Create a union of two CheckedInputObjects, ensuring consistency
852    /// for objects that appear in both sets. The base_set is consumed and
853    /// returned with the union. The other_set is borrowed.
854    /// In the case of shared objects, the mutability can differ, but the
855    /// initial shared version must match. For other object kinds, they must
856    /// match exactly.
857    fn checked_input_objects_union(
858        base_set: CheckedInputObjects,
859        other_set: &CheckedInputObjects,
860    ) -> IotaResult<CheckedInputObjects> {
861        let mut base_set = base_set.into_inner();
862        for other_object in other_set.inner().iter() {
863            if let Some(base_object) = base_set.find_object_id_mut(other_object.id()) {
864                // This is an invariant
865                assert_eq!(
866                    base_object.object, other_object.object,
867                    "The object read result for input objects with the same id must be equal"
868                );
869
870                // In the case of an alive object, check that the object kind matches exactly,
871                // or that, if it is a shared object, only the mutability changes
872                if let ObjectReadResultKind::Object(_) = &other_object.object {
873                    base_object
874                        .input_object_kind
875                        .left_union_with_checks(&other_object.input_object_kind)?;
876                }
877            } else {
878                base_set.push(other_object.clone());
879            }
880        }
881        Ok(base_set.into_checked())
882    }
883
884    /// Check package verification timeout
885    #[instrument(level = "trace", skip_all)]
886    pub fn check_non_system_packages_to_be_published(
887        transaction: &TransactionData,
888        protocol_config: &ProtocolConfig,
889        metrics: &Arc<BytecodeVerifierMetrics>,
890        verifier_signing_config: &VerifierSigningConfig,
891    ) -> UserInputResult<()> {
892        // Only meter non-system programmable transaction blocks
893        if transaction.is_system_tx() {
894            return Ok(());
895        }
896
897        let TransactionKind::Programmable(pt) = transaction.kind() else {
898            return Ok(());
899        };
900
901        // Use the same verifier and meter for all packages, custom configured for
902        // signing.
903        let signing_limits = Some(verifier_signing_config.limits_for_signing());
904        let mut verifier = iota_execution::verifier(protocol_config, signing_limits, metrics);
905        let mut meter = verifier.meter(verifier_signing_config.meter_config_for_signing());
906
907        // Measure time for verifying all packages in the PTB
908        let shared_meter_verifier_timer = metrics
909            .verifier_runtime_per_ptb_success_latency
910            .start_timer();
911
912        let verifier_status = pt
913            .non_system_packages_to_be_published()
914            .try_for_each(|module_bytes| {
915                verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
916            })
917            .map_err(|e| UserInputError::PackageVerificationTimedout { err: e.to_string() });
918
919        match verifier_status {
920            Ok(_) => {
921                // Success: stop and record the success timer
922                shared_meter_verifier_timer.stop_and_record();
923            }
924            Err(err) => {
925                // Failure: redirect the success timers output to the failure timer and
926                // discard the success timer
927                metrics
928                    .verifier_runtime_per_ptb_timeout_latency
929                    .observe(shared_meter_verifier_timer.stop_and_discard());
930                return Err(err);
931            }
932        };
933
934        Ok(())
935    }
936}