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_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    // 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_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                // 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 {
348            object_ref: (object_id, version, object_digest),
349            object,
350        } in receiving_objects.iter()
351        {
352            fp_ensure!(
353                *version < SequenceNumber::MAX_VALID_EXCL,
354                UserInputError::InvalidSequenceNumber.into()
355            );
356
357            let Some(object) = object.as_object() else {
358                // object was previously received
359                continue;
360            };
361
362            if !(object.owner.is_address_owned()
363                && object.version() == *version
364                && object.digest() == *object_digest)
365            {
366                // Version mismatch
367                fp_ensure!(
368                    object.version() == *version,
369                    UserInputError::ObjectVersionUnavailableForConsumption {
370                        provided_obj_ref: (*object_id, *version, *object_digest),
371                        current_version: object.version(),
372                    }
373                    .into()
374                );
375
376                // Tried to receive a package
377                fp_ensure!(
378                    !object.is_package(),
379                    UserInputError::MovePackageAsObject {
380                        object_id: *object_id
381                    }
382                    .into()
383                );
384
385                // Digest mismatch
386                let expected_digest = object.digest();
387                fp_ensure!(
388                    expected_digest == *object_digest,
389                    UserInputError::InvalidObjectDigest {
390                        object_id: *object_id,
391                        expected_digest
392                    }
393                    .into()
394                );
395
396                match object.owner {
397                    Owner::AddressOwner(_) => {
398                        debug_assert!(
399                            false,
400                            "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
401                            (*object_id, *version, *object_id),
402                            object
403                        );
404                        error!(
405                            "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
406                            (*object_id, *version, *object_id),
407                            object
408                        );
409                        // We should never get here, but if for some reason we do just default to
410                        // object not found and reject signing the transaction.
411                        fp_bail!(
412                            UserInputError::ObjectNotFound {
413                                object_id: *object_id,
414                                version: Some(*version),
415                            }
416                            .into()
417                        )
418                    }
419                    Owner::ObjectOwner(owner) => {
420                        fp_bail!(
421                            UserInputError::InvalidChildObjectArgument {
422                                child_id: object.id(),
423                                parent_id: owner.into(),
424                            }
425                            .into()
426                        )
427                    }
428                    Owner::Shared { .. } => fp_bail!(UserInputError::NotSharedObject.into()),
429                    Owner::Immutable => fp_bail!(
430                        UserInputError::MutableParameterExpected {
431                            object_id: *object_id
432                        }
433                        .into()
434                    ),
435                };
436            }
437
438            fp_ensure!(
439                !objects_in_txn.contains(object_id),
440                UserInputError::DuplicateObjectRefInput.into()
441            );
442
443            objects_in_txn.insert(*object_id);
444        }
445        Ok(())
446    }
447
448    /// Check transaction gas data/info and gas coins consistency.
449    /// Return the gas status to be used for the lifecycle of the transaction.
450    #[instrument(level = "trace", skip_all)]
451    fn check_gas(
452        objects: &InputObjects,
453        protocol_config: &ProtocolConfig,
454        reference_gas_price: u64,
455        gas: &[ObjectRef],
456        gas_price: u64,
457        transaction_gas_budget: u64,
458        authentication_gas_budget: u64,
459        is_execute_transaction_to_effects: bool,
460    ) -> IotaResult<IotaGasStatus> {
461        let gas_budget_to_set = if authentication_gas_budget > 0 {
462            // If there is an authentication gas budget, then we are checking if
463            // max_gas_budget is Some. If not, that is UserInputError.
464            let protocol_max_auth_gas =
465                protocol_config.max_auth_gas_as_option().ok_or_else(|| {
466                    UserInputError::Unsupported(
467                        "Transaction requires authentication gas but max_auth_gas is not enabled"
468                            .to_string(),
469                    )
470                })?;
471
472            // Execution phase:
473            //  - meter transaction + authentication;
474            //  - it needs the full budget.
475            // Signing phase:
476            //  - meter only authentication;
477            //  - it only needs authentication budget.
478            if is_execute_transaction_to_effects {
479                transaction_gas_budget
480            } else {
481                authentication_gas_budget.min(protocol_max_auth_gas)
482            }
483        } else {
484            // If there is no authentication gas budget, then we are only checking the
485            // transaction gas budget.
486            transaction_gas_budget
487        };
488
489        // Budget to check is always the one set by the user (which should cover full
490        // transaction + authentication costs).
491        let gas_budget_to_check = transaction_gas_budget;
492
493        let gas_status = IotaGasStatus::new(
494            gas_budget_to_set,
495            gas_price,
496            reference_gas_price,
497            protocol_config,
498        )?;
499
500        // Check balance and coins consistency
501        // Load all gas coins
502        let objects: BTreeMap<_, _> = objects.iter().map(|o| (o.id(), o)).collect();
503        let mut gas_objects = vec![];
504        for obj_ref in gas {
505            let obj = objects.get(&obj_ref.0);
506            let obj = *obj.ok_or(UserInputError::ObjectNotFound {
507                object_id: obj_ref.0,
508                version: Some(obj_ref.1),
509            })?;
510            gas_objects.push(obj);
511        }
512        gas_status.check_gas_balance(&gas_objects, gas_budget_to_check)?;
513        Ok(gas_status)
514    }
515
516    /// Check all the objects used in the transaction against the database, and
517    /// ensure that they are all the correct version and number.
518    #[instrument(level = "trace", skip_all)]
519    fn check_objects(transaction: &TransactionData, objects: &InputObjects) -> UserInputResult<()> {
520        // We require that mutable objects cannot show up more than once.
521        let mut used_objects: HashSet<IotaAddress> = HashSet::new();
522        for object in objects.iter() {
523            if object.is_mutable() {
524                fp_ensure!(
525                    used_objects.insert(object.id().into()),
526                    UserInputError::MutableObjectUsedMoreThanOnce {
527                        object_id: object.id()
528                    }
529                );
530            }
531        }
532
533        if !transaction.is_genesis_tx() && objects.is_empty() {
534            return Err(UserInputError::ObjectInputArityViolation);
535        }
536
537        let gas_coins: HashSet<ObjectID> =
538            HashSet::from_iter(transaction.gas().iter().map(|obj_ref| obj_ref.0));
539        for object in objects.iter() {
540            let input_object_kind = object.input_object_kind;
541
542            match &object.object {
543                ObjectReadResultKind::Object(object) => {
544                    // For Gas Object, we check the object is owned by gas owner
545                    let owner_address = if gas_coins.contains(&object.id()) {
546                        transaction.gas_owner()
547                    } else {
548                        transaction.sender()
549                    };
550                    // Check if the object contents match the type of lock we need for
551                    // this object.
552                    let system_transaction = transaction.is_system_tx();
553                    check_one_object(
554                        &owner_address,
555                        input_object_kind,
556                        object,
557                        system_transaction,
558                    )?;
559                }
560                // We skip checking a deleted shared object because it no longer exists
561                ObjectReadResultKind::DeletedSharedObject(_, _) => (),
562                // We skip checking shared objects from cancelled transactions since we are not
563                // reading it.
564                ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
565            }
566        }
567
568        Ok(())
569    }
570
571    /// Check one object against a reference
572    fn check_one_object(
573        owner: &IotaAddress,
574        object_kind: InputObjectKind,
575        object: &Object,
576        system_transaction: bool,
577    ) -> UserInputResult {
578        match object_kind {
579            InputObjectKind::MovePackage(package_id) => {
580                fp_ensure!(
581                    object.data.try_as_package().is_some(),
582                    UserInputError::MoveObjectAsPackage {
583                        object_id: package_id
584                    }
585                );
586            }
587            InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
588                fp_ensure!(
589                    !object.is_package(),
590                    UserInputError::MovePackageAsObject { object_id }
591                );
592                fp_ensure!(
593                    sequence_number < SequenceNumber::MAX_VALID_EXCL,
594                    UserInputError::InvalidSequenceNumber
595                );
596
597                // This is an invariant - we just load the object with the given ID and version.
598                assert_eq!(
599                    object.version(),
600                    sequence_number,
601                    "The fetched object version {} does not match the requested version {}, object id: {}",
602                    object.version(),
603                    sequence_number,
604                    object.id(),
605                );
606
607                // Check the digest matches - user could give a mismatched ObjectDigest
608                let expected_digest = object.digest();
609                fp_ensure!(
610                    expected_digest == object_digest,
611                    UserInputError::InvalidObjectDigest {
612                        object_id,
613                        expected_digest
614                    }
615                );
616
617                match object.owner {
618                    Owner::Immutable => {
619                        // Nothing else to check for Immutable.
620                    }
621                    Owner::AddressOwner(actual_owner) => {
622                        // Check the owner is correct.
623                        fp_ensure!(
624                            owner == &actual_owner,
625                            UserInputError::IncorrectUserSignature {
626                                error: format!(
627                                    "Object {object_id:?} is owned by account address {actual_owner}, but given owner/signer address is {owner}"
628                                ),
629                            }
630                        );
631                    }
632                    Owner::ObjectOwner(owner) => {
633                        return Err(UserInputError::InvalidChildObjectArgument {
634                            child_id: object.id(),
635                            parent_id: owner.into(),
636                        });
637                    }
638                    Owner::Shared { .. } => {
639                        // This object is a mutable shared object. However the transaction
640                        // specifies it as an owned object. This is inconsistent.
641                        return Err(UserInputError::NotSharedObject);
642                    }
643                };
644            }
645            InputObjectKind::SharedMoveObject {
646                id: IOTA_CLOCK_OBJECT_ID,
647                initial_shared_version: IOTA_CLOCK_OBJECT_SHARED_VERSION,
648                mutable: true,
649            } => {
650                // Only system transactions can accept the Clock
651                // object as a mutable parameter.
652                if system_transaction {
653                    return Ok(());
654                } else {
655                    return Err(UserInputError::ImmutableParameterExpected {
656                        object_id: IOTA_CLOCK_OBJECT_ID,
657                    });
658                }
659            }
660            InputObjectKind::SharedMoveObject {
661                id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
662                ..
663            } => {
664                if system_transaction {
665                    return Ok(());
666                } else {
667                    return Err(UserInputError::InaccessibleSystemObject {
668                        object_id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
669                    });
670                }
671            }
672            InputObjectKind::SharedMoveObject {
673                id: IOTA_RANDOMNESS_STATE_OBJECT_ID,
674                mutable: true,
675                ..
676            } => {
677                // Only system transactions can accept the Random
678                // object as a mutable parameter.
679                if system_transaction {
680                    return Ok(());
681                } else {
682                    return Err(UserInputError::ImmutableParameterExpected {
683                        object_id: IOTA_RANDOMNESS_STATE_OBJECT_ID,
684                    });
685                }
686            }
687            InputObjectKind::SharedMoveObject {
688                initial_shared_version: input_initial_shared_version,
689                ..
690            } => {
691                fp_ensure!(
692                    object.version() < SequenceNumber::MAX_VALID_EXCL,
693                    UserInputError::InvalidSequenceNumber
694                );
695
696                match object.owner {
697                    Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
698                        // When someone locks an object as shared it must be shared already.
699                        return Err(UserInputError::NotSharedObject);
700                    }
701                    Owner::Shared {
702                        initial_shared_version: actual_initial_shared_version,
703                    } => {
704                        fp_ensure!(
705                            input_initial_shared_version == actual_initial_shared_version,
706                            UserInputError::SharedObjectStartingVersionMismatch
707                        )
708                    }
709                }
710            }
711        };
712        Ok(())
713    }
714
715    /// Check all the `MoveAuthenticator` related input objects against the
716    /// database.
717    #[instrument(level = "trace", skip_all)]
718    fn check_move_authenticator_objects(
719        authenticator_objects: &InputObjects,
720    ) -> UserInputResult<()> {
721        for object in authenticator_objects.iter() {
722            let input_object_kind = object.input_object_kind;
723
724            match &object.object {
725                ObjectReadResultKind::Object(object) => {
726                    check_one_move_authenticator_object(input_object_kind, object)?;
727                }
728                // We skip checking a deleted shared object because it no longer exists.
729                ObjectReadResultKind::DeletedSharedObject(_, _) => (),
730                // We skip checking shared objects from cancelled transactions since we are not
731                // reading it.
732                ObjectReadResultKind::CancelledTransactionSharedObject(_) => (),
733            }
734        }
735
736        Ok(())
737    }
738
739    /// Check one `MoveAuthenticator` input object.
740    fn check_one_move_authenticator_object(
741        object_kind: InputObjectKind,
742        object: &Object,
743    ) -> UserInputResult {
744        match object_kind {
745            InputObjectKind::MovePackage(package_id) => {
746                return Err(UserInputError::PackageIsInMoveAuthenticatorInput { package_id });
747            }
748            InputObjectKind::ImmOrOwnedMoveObject((object_id, sequence_number, object_digest)) => {
749                fp_ensure!(
750                    !object.is_package(),
751                    UserInputError::MovePackageAsObject { object_id }
752                );
753                fp_ensure!(
754                    sequence_number < SequenceNumber::MAX_VALID_EXCL,
755                    UserInputError::InvalidSequenceNumber
756                );
757
758                // This is an invariant - we just load the object with the given ID and version.
759                assert_eq!(
760                    object.version(),
761                    sequence_number,
762                    "The fetched object version {} does not match the requested version {}, object id: {}",
763                    object.version(),
764                    sequence_number,
765                    object.id(),
766                );
767
768                // Check the digest matches - user could give a mismatched `ObjectDigest`.
769                let expected_digest = object.digest();
770                fp_ensure!(
771                    expected_digest == object_digest,
772                    UserInputError::InvalidObjectDigest {
773                        object_id,
774                        expected_digest
775                    }
776                );
777
778                match object.owner {
779                    Owner::Immutable => {
780                        // Nothing else to check for Immutable.
781                    }
782                    Owner::AddressOwner { .. } => {
783                        return Err(UserInputError::AddressOwnedIsInMoveAuthenticatorInput {
784                            object_id: object.id(),
785                        });
786                    }
787                    Owner::ObjectOwner { .. } => {
788                        return Err(UserInputError::ObjectOwnedIsInMoveAuthenticatorInput {
789                            object_id: object.id(),
790                        });
791                    }
792                    Owner::Shared { .. } => {
793                        // This object is a mutable shared object. However the transaction
794                        // specifies it as an owned object. This is inconsistent.
795                        return Err(UserInputError::NotSharedObject);
796                    }
797                };
798            }
799            InputObjectKind::SharedMoveObject {
800                id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
801                ..
802            } => {
803                return Err(UserInputError::InaccessibleSystemObject {
804                    object_id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
805                });
806            }
807            InputObjectKind::SharedMoveObject {
808                id, mutable: true, ..
809            } => {
810                return Err(UserInputError::MutableSharedIsInMoveAuthenticatorInput {
811                    object_id: id,
812                });
813            }
814            InputObjectKind::SharedMoveObject {
815                initial_shared_version: input_initial_shared_version,
816                ..
817            } => {
818                fp_ensure!(
819                    object.version() < SequenceNumber::MAX_VALID_EXCL,
820                    UserInputError::InvalidSequenceNumber
821                );
822
823                match object.owner {
824                    Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable => {
825                        // When someone locks an object as shared it must be shared already.
826                        return Err(UserInputError::NotSharedObject);
827                    }
828                    Owner::Shared {
829                        initial_shared_version: actual_initial_shared_version,
830                    } => {
831                        fp_ensure!(
832                            input_initial_shared_version == actual_initial_shared_version,
833                            UserInputError::SharedObjectStartingVersionMismatch
834                        )
835                    }
836                }
837            }
838        };
839        Ok(())
840    }
841
842    /// Create a union of two CheckedInputObjects, ensuring consistency
843    /// for objects that appear in both sets. The base_set is consumed and
844    /// returned with the union. The other_set is borrowed.
845    /// In the case of shared objects, the mutability can differ, but the
846    /// initial shared version must match. For other object kinds, they must
847    /// match exactly.
848    fn checked_input_objects_union(
849        base_set: CheckedInputObjects,
850        other_set: &CheckedInputObjects,
851    ) -> IotaResult<CheckedInputObjects> {
852        let mut base_set = base_set.into_inner();
853        for other_object in other_set.inner().iter() {
854            if let Some(base_object) = base_set.find_object_id_mut(other_object.id()) {
855                // This is an invariant
856                assert_eq!(
857                    base_object.object, other_object.object,
858                    "The object read result for input objects with the same id must be equal"
859                );
860
861                // In the case of an alive object, check that the object kind matches exactly,
862                // or that, if it is a shared object, only the mutability changes
863                if let ObjectReadResultKind::Object(_) = &other_object.object {
864                    base_object
865                        .input_object_kind
866                        .left_union_with_checks(&other_object.input_object_kind)?;
867                }
868            } else {
869                base_set.push(other_object.clone());
870            }
871        }
872        Ok(base_set.into_checked())
873    }
874
875    /// Check package verification timeout
876    #[instrument(level = "trace", skip_all)]
877    pub fn check_non_system_packages_to_be_published(
878        transaction: &TransactionData,
879        protocol_config: &ProtocolConfig,
880        metrics: &Arc<BytecodeVerifierMetrics>,
881        verifier_signing_config: &VerifierSigningConfig,
882    ) -> UserInputResult<()> {
883        // Only meter non-system programmable transaction blocks
884        if transaction.is_system_tx() {
885            return Ok(());
886        }
887
888        let TransactionKind::ProgrammableTransaction(pt) = transaction.kind() else {
889            return Ok(());
890        };
891
892        // Use the same verifier and meter for all packages, custom configured for
893        // signing.
894        let signing_limits = Some(verifier_signing_config.limits_for_signing());
895        let mut verifier = iota_execution::verifier(protocol_config, signing_limits, metrics);
896        let mut meter = verifier.meter(verifier_signing_config.meter_config_for_signing());
897
898        // Measure time for verifying all packages in the PTB
899        let shared_meter_verifier_timer = metrics
900            .verifier_runtime_per_ptb_success_latency
901            .start_timer();
902
903        let verifier_status = pt
904            .non_system_packages_to_be_published()
905            .try_for_each(|module_bytes| {
906                verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
907            })
908            .map_err(|e| UserInputError::PackageVerificationTimedout { err: e.to_string() });
909
910        match verifier_status {
911            Ok(_) => {
912                // Success: stop and record the success timer
913                shared_meter_verifier_timer.stop_and_record();
914            }
915            Err(err) => {
916                // Failure: redirect the success timers output to the failure timer and
917                // discard the success timer
918                metrics
919                    .verifier_runtime_per_ptb_timeout_latency
920                    .observe(shared_meter_verifier_timer.stop_and_discard());
921                return Err(err);
922            }
923        };
924
925        Ok(())
926    }
927}