Skip to main content

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