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::CancelledTransactionObject(_) => (),
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                id: ObjectId::TRANSACTION_DENY_RULES,
695                ..
696            } => {
697                // The deny rules object is written only by system
698                // transactions and has no user-callable readers.
699                if system_transaction {
700                    return Ok(());
701                } else {
702                    return Err(UserInputError::InaccessibleSystemObject {
703                        object_id: ObjectId::TRANSACTION_DENY_RULES,
704                    });
705                }
706            }
707            InputObjectKind::SharedMoveObject {
708                initial_shared_version: input_initial_shared_version,
709                ..
710            } => {
711                fp_ensure!(
712                    object.version() < Version::MAX_VALID_EXCL,
713                    UserInputError::InvalidSequenceNumber
714                );
715
716                match object.owner {
717                    Owner::Address(_) | Owner::Object(_) | Owner::Immutable => {
718                        // When someone locks an object as shared it must be shared already.
719                        return Err(UserInputError::NotSharedObject);
720                    }
721                    Owner::Shared(actual_initial_shared_version) => {
722                        fp_ensure!(
723                            input_initial_shared_version == actual_initial_shared_version,
724                            UserInputError::SharedObjectStartingVersionMismatch
725                        )
726                    }
727                    _ => {
728                        unimplemented!("a new Owner enum variant was added and needs to be handled")
729                    }
730                }
731            }
732        };
733        Ok(())
734    }
735
736    /// Check all the `MoveAuthenticator` related input objects against the
737    /// database.
738    #[instrument(level = "trace", skip_all)]
739    fn check_move_authenticator_objects(
740        authenticator_objects: &InputObjects,
741    ) -> UserInputResult<()> {
742        for object in authenticator_objects.iter() {
743            let input_object_kind = object.input_object_kind;
744
745            match &object.object {
746                ObjectReadResultKind::Object(object) => {
747                    check_one_move_authenticator_object(input_object_kind, object)?;
748                }
749                // We skip checking a deleted shared object because it no longer exists.
750                ObjectReadResultKind::DeletedSharedObject(_, _) => (),
751                // We skip checking shared objects from cancelled transactions since we are not
752                // reading it.
753                ObjectReadResultKind::CancelledTransactionObject(_) => (),
754            }
755        }
756
757        Ok(())
758    }
759
760    /// Check one `MoveAuthenticator` input object.
761    fn check_one_move_authenticator_object(
762        object_kind: InputObjectKind,
763        object: &Object,
764    ) -> UserInputResult {
765        match object_kind {
766            InputObjectKind::MovePackage(package_id) => {
767                return Err(UserInputError::PackageIsInMoveAuthenticatorInput { package_id });
768            }
769            InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
770                fp_ensure!(
771                    !object.is_package(),
772                    UserInputError::MovePackageAsObject {
773                        object_id: object_ref.object_id
774                    }
775                );
776                fp_ensure!(
777                    object_ref.version < Version::MAX_VALID_EXCL,
778                    UserInputError::InvalidSequenceNumber
779                );
780
781                // This is an invariant - we just load the object with the given ID and version.
782                assert_eq!(
783                    object.version(),
784                    object_ref.version,
785                    "The fetched object version {} does not match the requested version {}, object id: {}",
786                    object.version(),
787                    object_ref.version,
788                    object.id(),
789                );
790
791                // Check the digest matches - user could give a mismatched `ObjectDigest`.
792                let expected_digest = object.digest();
793                fp_ensure!(
794                    expected_digest == object_ref.digest,
795                    UserInputError::InvalidObjectDigest {
796                        object_id: object_ref.object_id,
797                        expected_digest
798                    }
799                );
800
801                match object.owner {
802                    Owner::Immutable => {
803                        // Nothing else to check for Immutable.
804                    }
805                    Owner::Address(_) => {
806                        return Err(UserInputError::AddressOwnedIsInMoveAuthenticatorInput {
807                            object_id: object.id(),
808                        });
809                    }
810                    Owner::Object(_) => {
811                        return Err(UserInputError::ObjectOwnedIsInMoveAuthenticatorInput {
812                            object_id: object.id(),
813                        });
814                    }
815                    Owner::Shared(_) => {
816                        // This object is a mutable shared object. However the transaction
817                        // specifies it as an owned object. This is inconsistent.
818                        return Err(UserInputError::NotSharedObject);
819                    }
820                    _ => {
821                        unimplemented!("a new Owner enum variant was added and needs to be handled")
822                    }
823                };
824            }
825            InputObjectKind::SharedMoveObject {
826                id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
827                ..
828            } => {
829                return Err(UserInputError::InaccessibleSystemObject {
830                    object_id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID,
831                });
832            }
833            InputObjectKind::SharedMoveObject {
834                id, mutable: true, ..
835            } => {
836                return Err(UserInputError::MutableSharedIsInMoveAuthenticatorInput {
837                    object_id: id,
838                });
839            }
840            InputObjectKind::SharedMoveObject {
841                initial_shared_version: input_initial_shared_version,
842                ..
843            } => {
844                fp_ensure!(
845                    object.version() < Version::MAX_VALID_EXCL,
846                    UserInputError::InvalidSequenceNumber
847                );
848
849                match object.owner {
850                    Owner::Address(_) | Owner::Object(_) | Owner::Immutable => {
851                        // When someone locks an object as shared it must be shared already.
852                        return Err(UserInputError::NotSharedObject);
853                    }
854                    Owner::Shared(actual_initial_shared_version) => {
855                        fp_ensure!(
856                            input_initial_shared_version == actual_initial_shared_version,
857                            UserInputError::SharedObjectStartingVersionMismatch
858                        )
859                    }
860                    _ => {
861                        unimplemented!("a new Owner enum variant was added and needs to be handled")
862                    }
863                }
864            }
865        };
866        Ok(())
867    }
868
869    /// Create a union of two CheckedInputObjects, ensuring consistency
870    /// for objects that appear in both sets. The base_set is consumed and
871    /// returned with the union. The other_set is borrowed.
872    /// In the case of shared objects, the mutability can differ, but the
873    /// initial shared version must match. For other object kinds, they must
874    /// match exactly.
875    pub fn checked_input_objects_union(
876        base_set: CheckedInputObjects,
877        other_set: &CheckedInputObjects,
878    ) -> IotaResult<CheckedInputObjects> {
879        let mut base_set = base_set.into_inner();
880        for other_object in other_set.inner().iter() {
881            if let Some(base_object) = base_set.find_object_id_mut(other_object.id()) {
882                // This is an invariant
883                assert_eq!(
884                    base_object.object, other_object.object,
885                    "The object read result for input objects with the same id must be equal"
886                );
887
888                // In the case of an alive object, check that the object kind matches exactly,
889                // or that, if it is a shared object, only the mutability changes
890                if let ObjectReadResultKind::Object(_) = &other_object.object {
891                    base_object
892                        .input_object_kind
893                        .left_union_with_checks(&other_object.input_object_kind)?;
894                }
895            } else {
896                base_set.push(other_object.clone());
897            }
898        }
899        Ok(base_set.into_checked())
900    }
901
902    /// Check package verification timeout
903    #[instrument(level = "trace", skip_all)]
904    pub fn check_non_system_packages_to_be_published(
905        transaction: &Transaction,
906        protocol_config: &ProtocolConfig,
907        metrics: &Arc<BytecodeVerifierMetrics>,
908        verifier_signing_config: &VerifierSigningConfig,
909    ) -> UserInputResult<()> {
910        // Only meter non-system programmable transaction blocks
911        if transaction.is_system_tx() {
912            return Ok(());
913        }
914
915        let TransactionKind::Programmable(pt) = transaction.kind() else {
916            return Ok(());
917        };
918
919        // Use the same verifier and meter for all packages, custom configured for
920        // signing.
921        let signing_limits = Some(verifier_signing_config.limits_for_signing());
922        let mut verifier = iota_execution::verifier(protocol_config, signing_limits, metrics);
923        let mut meter = verifier.meter(verifier_signing_config.meter_config_for_signing());
924
925        // Measure time for verifying all packages in the PTB
926        let shared_meter_verifier_timer = metrics
927            .verifier_runtime_per_ptb_success_latency
928            .start_timer();
929
930        let verifier_status = pt
931            .non_system_packages_to_be_published()
932            .try_for_each(|module_bytes| {
933                verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
934            })
935            .map_err(|e| UserInputError::PackageVerificationTimedout { err: e.to_string() });
936
937        match verifier_status {
938            Ok(_) => {
939                // Success: stop and record the success timer
940                shared_meter_verifier_timer.stop_and_record();
941            }
942            Err(err) => {
943                // Failure: redirect the success timers output to the failure timer and
944                // discard the success timer
945                metrics
946                    .verifier_runtime_per_ptb_timeout_latency
947                    .observe(shared_meter_verifier_timer.stop_and_discard());
948                return Err(err);
949            }
950        };
951
952        Ok(())
953    }
954}