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