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