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_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    fn input_object_ids(input_objects: &InputObjects) -> HashSet<ObjectId> {
349        input_objects
350            .object_kinds()
351            .map(|x| x.object_id())
352            .collect()
353    }
354
355    fn check_receiving_version_bound(object_ref: &ObjectReference) -> Result<(), IotaError> {
356        fp_ensure!(
357            object_ref.version < Version::MAX_VALID_EXCL,
358            UserInputError::InvalidSequenceNumber.into()
359        );
360        Ok(())
361    }
362
363    fn check_not_already_in_transaction(
364        objects_in_txn: &mut HashSet<ObjectId>,
365        object_ref: &ObjectReference,
366    ) -> Result<(), IotaError> {
367        fp_ensure!(
368            objects_in_txn.insert(object_ref.object_id),
369            UserInputError::DuplicateObjectRefInput.into()
370        );
371        Ok(())
372    }
373
374    #[instrument(level = "trace", skip_all)]
375    fn check_receiving_objects(
376        input_objects: &InputObjects,
377        receiving_objects: &ReceivingObjects,
378    ) -> Result<(), IotaError> {
379        let mut objects_in_txn = input_object_ids(input_objects);
380
381        // Since we're at signing we check that every object reference that we are
382        // receiving is the most recent version of that object. If it's been
383        // received at the version specified we let it through to allow the
384        // transaction to run and fail to unlock any other objects in
385        // the transaction. Otherwise, we return an error.
386        //
387        // If there are any object IDs in common (either between receiving objects and
388        // input objects) we return an error.
389        for ReceivingObjectReadResult { object_ref, object } in receiving_objects.iter() {
390            check_receiving_version_bound(object_ref)?;
391
392            let Some(object) = object.as_object() else {
393                // object was previously received
394                continue;
395            };
396
397            if !(object.owner.is_address()
398                && object.version() == object_ref.version
399                && object.digest() == object_ref.digest)
400            {
401                // Version mismatch
402                fp_ensure!(
403                    object.version() == object_ref.version,
404                    UserInputError::ObjectVersionUnavailableForConsumption {
405                        provided_obj_ref: *object_ref,
406                        current_version: object.version(),
407                    }
408                    .into()
409                );
410
411                // Tried to receive a package
412                fp_ensure!(
413                    !object.is_package(),
414                    UserInputError::MovePackageAsObject {
415                        object_id: object_ref.object_id
416                    }
417                    .into()
418                );
419
420                // Digest mismatch
421                let expected_digest = object.digest();
422                fp_ensure!(
423                    expected_digest == object_ref.digest,
424                    UserInputError::InvalidObjectDigest {
425                        object_id: object_ref.object_id,
426                        expected_digest
427                    }
428                    .into()
429                );
430
431                match object.owner {
432                    Owner::Address(_) => {
433                        debug_assert!(
434                            false,
435                            "Receiving object {object_ref:?} is invalid but we expect it should be valid. {object:?}"
436                        );
437                        error!(
438                            "Receiving object {:?} is invalid but we expect it should be valid. {:?}",
439                            object_ref, object
440                        );
441                        // We should never get here, but if for some reason we do just default to
442                        // object not found and reject signing the transaction.
443                        fp_bail!(
444                            UserInputError::ObjectNotFound {
445                                object_id: object_ref.object_id,
446                                version: Some(object_ref.version),
447                            }
448                            .into()
449                        )
450                    }
451                    Owner::Object(owner) => {
452                        fp_bail!(
453                            UserInputError::InvalidChildObjectArgument {
454                                child_id: object.id(),
455                                parent_id: owner,
456                            }
457                            .into()
458                        )
459                    }
460                    Owner::Shared(_) => fp_bail!(UserInputError::NotSharedObject.into()),
461                    Owner::Immutable => fp_bail!(
462                        UserInputError::MutableParameterExpected {
463                            object_id: object_ref.object_id
464                        }
465                        .into()
466                    ),
467                    _ => {
468                        unimplemented!("a new Owner enum variant was added and needs to be handled")
469                    }
470                };
471            }
472
473            check_not_already_in_transaction(&mut objects_in_txn, object_ref)?;
474        }
475        Ok(())
476    }
477
478    /// Check transaction gas data/info and gas coins consistency.
479    /// Return the gas status to be used for the lifecycle of the transaction.
480    #[instrument(level = "trace", skip_all)]
481    fn check_gas(
482        objects: &InputObjects,
483        protocol_config: &ProtocolConfig,
484        reference_gas_price: u64,
485        gas: &[ObjectReference],
486        gas_price: u64,
487        transaction_gas_budget: u64,
488        authentication_gas_budget: u64,
489        is_execute_transaction_to_effects: bool,
490    ) -> IotaResult<IotaGasStatus> {
491        let gas_budget_to_set = if authentication_gas_budget > 0 {
492            // If there is an authentication gas budget, then we are checking if
493            // max_gas_budget is Some. If not, that is UserInputError.
494            let protocol_max_auth_gas =
495                protocol_config.max_auth_gas_as_option().ok_or_else(|| {
496                    UserInputError::Unsupported(
497                        "Transaction requires authentication gas but max_auth_gas is not enabled"
498                            .to_string(),
499                    )
500                })?;
501
502            // Execution phase:
503            //  - meter transaction + authentication;
504            //  - it needs the full budget.
505            // Signing phase:
506            //  - meter only authentication;
507            //  - it only needs authentication budget.
508            if is_execute_transaction_to_effects {
509                transaction_gas_budget
510            } else {
511                authentication_gas_budget.min(protocol_max_auth_gas)
512            }
513        } else {
514            // If there is no authentication gas budget, then we are only checking the
515            // transaction gas budget.
516            transaction_gas_budget
517        };
518
519        // Budget to check is always the one set by the user (which should cover full
520        // transaction + authentication costs).
521        let gas_budget_to_check = transaction_gas_budget;
522
523        let gas_status = IotaGasStatus::new(
524            gas_budget_to_set,
525            gas_price,
526            reference_gas_price,
527            protocol_config,
528        )?;
529
530        // Check balance and coins consistency
531        // Load all gas coins
532        let objects: BTreeMap<_, _> = objects.iter().map(|o| (o.id(), o)).collect();
533        let mut gas_objects = vec![];
534        for obj_ref in gas {
535            let obj = objects.get(&obj_ref.object_id);
536            let obj = *obj.ok_or(UserInputError::ObjectNotFound {
537                object_id: obj_ref.object_id,
538                version: Some(obj_ref.version),
539            })?;
540            gas_objects.push(obj);
541        }
542        gas_status.check_gas_balance(&gas_objects, gas_budget_to_check)?;
543        Ok(gas_status)
544    }
545
546    /// Check all the objects used in the transaction against the database, and
547    /// ensure that they are all the correct version and number.
548    #[instrument(level = "trace", skip_all)]
549    fn check_objects(transaction: &Transaction, objects: &InputObjects) -> UserInputResult<()> {
550        // We require that mutable objects cannot show up more than once.
551        let mut used_objects: HashSet<Address> = HashSet::new();
552        for object in objects.iter() {
553            if object.is_mutable() {
554                fp_ensure!(
555                    used_objects.insert(object.id().into()),
556                    UserInputError::MutableObjectUsedMoreThanOnce {
557                        object_id: object.id()
558                    }
559                );
560            }
561        }
562
563        if !transaction.is_genesis_tx() && objects.is_empty() {
564            return Err(UserInputError::ObjectInputArityViolation);
565        }
566
567        let gas_coins: HashSet<ObjectId> =
568            HashSet::from_iter(transaction.gas().iter().map(|obj_ref| obj_ref.object_id));
569        for object in objects.iter() {
570            let input_object_kind = object.input_object_kind;
571
572            match &object.object {
573                ObjectReadResultKind::Object(object) => {
574                    // For Gas Object, we check the object is owned by gas owner
575                    let owner_address = if gas_coins.contains(&object.id()) {
576                        transaction.gas_owner()
577                    } else {
578                        transaction.sender()
579                    };
580                    // Check if the object contents match the type of lock we need for
581                    // this object.
582                    let system_transaction = transaction.is_system_tx();
583                    check_one_object(
584                        &owner_address,
585                        input_object_kind,
586                        object,
587                        system_transaction,
588                    )?;
589                }
590                // We skip checking a deleted shared object because it no longer exists
591                ObjectReadResultKind::DeletedSharedObject(_, _) => (),
592                // We skip checking shared objects from cancelled transactions since we are not
593                // reading it.
594                ObjectReadResultKind::CancelledTransactionObject(_) => (),
595                // An unread shared object has no contents to check. Only the rules on how
596                // the transaction declares it apply.
597                ObjectReadResultKind::UnreadSharedObject => {
598                    check_shared_object_declaration(
599                        &input_object_kind,
600                        transaction.is_system_tx(),
601                    )?;
602                }
603            }
604        }
605
606        Ok(())
607    }
608
609    /// Check one object against a reference
610    fn check_one_object(
611        owner: &Address,
612        object_kind: InputObjectKind,
613        object: &Object,
614        system_transaction: bool,
615    ) -> UserInputResult {
616        match object_kind {
617            InputObjectKind::MovePackage(package_id) => {
618                fp_ensure!(
619                    object.data.as_opt_package().is_some(),
620                    UserInputError::MoveObjectAsPackage {
621                        object_id: package_id
622                    }
623                );
624            }
625            InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
626                fp_ensure!(
627                    !object.is_package(),
628                    UserInputError::MovePackageAsObject {
629                        object_id: object_ref.object_id
630                    }
631                );
632                fp_ensure!(
633                    object_ref.version < Version::MAX_VALID_EXCL,
634                    UserInputError::InvalidSequenceNumber
635                );
636
637                // This is an invariant - we just load the object with the given ID and version.
638                assert_eq!(
639                    object.version(),
640                    object_ref.version,
641                    "The fetched object version {} does not match the requested version {}, object id: {}",
642                    object.version(),
643                    object_ref.version,
644                    object.id(),
645                );
646
647                // Check the digest matches - user could give a mismatched ObjectDigest
648                let expected_digest = object.digest();
649                fp_ensure!(
650                    expected_digest == object_ref.digest,
651                    UserInputError::InvalidObjectDigest {
652                        object_id: object_ref.object_id,
653                        expected_digest
654                    }
655                );
656
657                match object.owner {
658                    Owner::Immutable => {
659                        // Nothing else to check for Immutable.
660                    }
661                    Owner::Address(actual_owner) => {
662                        // Check the owner is correct.
663                        fp_ensure!(
664                            owner == &actual_owner,
665                            UserInputError::IncorrectUserSignature {
666                                error: format!(
667                                    "Object {} is owned by account address {}, but given owner/signer address is {}",
668                                    object_ref.object_id, actual_owner, owner
669                                ),
670                            }
671                        );
672                    }
673                    Owner::Object(owner) => {
674                        return Err(UserInputError::InvalidChildObjectArgument {
675                            child_id: object.id(),
676                            parent_id: owner,
677                        });
678                    }
679                    Owner::Shared(_) => {
680                        // This object is a mutable shared object. However the transaction
681                        // specifies it as an owned object. This is inconsistent.
682                        return Err(UserInputError::NotSharedObject);
683                    }
684                    _ => {
685                        unimplemented!("a new Owner enum variant was added and needs to be handled")
686                    }
687                };
688            }
689            InputObjectKind::SharedMoveObject {
690                initial_shared_version: input_initial_shared_version,
691                ..
692            } => {
693                if check_shared_object_declaration(&object_kind, system_transaction)? {
694                    return Ok(());
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
720        Ok(())
721    }
722
723    /// Checks a shared input against the rules on system objects, which depend
724    /// only on how the transaction declares it: only a system transaction may
725    /// name the authenticator state or the deny rules object, or name the clock
726    /// or the randomness state as mutable. Fails if a rule forbids the input.
727    /// Returns `true` if a rule allows it, so no further check is needed, and
728    /// `false` if no rule covers it.
729    fn check_shared_object_declaration(
730        object_kind: &InputObjectKind,
731        system_transaction: bool,
732    ) -> UserInputResult<bool> {
733        match object_kind {
734            InputObjectKind::SharedMoveObject {
735                id: ObjectId::CLOCK,
736                initial_shared_version: IOTA_CLOCK_OBJECT_SHARED_VERSION,
737                mutable: true,
738            } => {
739                // Only system transactions can accept the Clock
740                // object as a mutable parameter.
741                if system_transaction {
742                    Ok(true)
743                } else {
744                    Err(UserInputError::ImmutableParameterExpected {
745                        object_id: ObjectId::CLOCK,
746                    })
747                }
748            }
749            InputObjectKind::SharedMoveObject {
750                id: ObjectId::AUTHENTICATOR_STATE,
751                ..
752            } => {
753                if system_transaction {
754                    Ok(true)
755                } else {
756                    Err(UserInputError::InaccessibleSystemObject {
757                        object_id: ObjectId::AUTHENTICATOR_STATE,
758                    })
759                }
760            }
761            InputObjectKind::SharedMoveObject {
762                id: ObjectId::RANDOMNESS_STATE,
763                mutable: true,
764                ..
765            } => {
766                // Only system transactions can accept the Random
767                // object as a mutable parameter.
768                if system_transaction {
769                    Ok(true)
770                } else {
771                    Err(UserInputError::ImmutableParameterExpected {
772                        object_id: ObjectId::RANDOMNESS_STATE,
773                    })
774                }
775            }
776            InputObjectKind::SharedMoveObject {
777                id: ObjectId::TRANSACTION_DENY_RULES,
778                ..
779            } => {
780                // The deny rules object is written only by system
781                // transactions and has no user-callable readers.
782                if system_transaction {
783                    Ok(true)
784                } else {
785                    Err(UserInputError::InaccessibleSystemObject {
786                        object_id: ObjectId::TRANSACTION_DENY_RULES,
787                    })
788                }
789            }
790            _ => Ok(false),
791        }
792    }
793
794    /// Check all the `MoveAuthenticator` related input objects against the
795    /// database. An unread shared object is checked only on how it is
796    /// declared.
797    #[instrument(level = "trace", skip_all)]
798    fn check_move_authenticator_objects(
799        authenticator_objects: &InputObjects,
800    ) -> UserInputResult<()> {
801        for object in authenticator_objects.iter() {
802            let input_object_kind = object.input_object_kind;
803
804            match &object.object {
805                ObjectReadResultKind::Object(object) => {
806                    check_one_move_authenticator_object(input_object_kind, object)?;
807                }
808                // We skip checking a deleted shared object because it no longer exists.
809                ObjectReadResultKind::DeletedSharedObject(_, _) => (),
810                // We skip checking shared objects from cancelled transactions since we are not
811                // reading it.
812                ObjectReadResultKind::CancelledTransactionObject(_) => (),
813                // An unread shared object has no contents to check. Only the rules on how
814                // the authenticator declares it apply.
815                ObjectReadResultKind::UnreadSharedObject => {
816                    check_move_authenticator_shared_object_declaration(&input_object_kind)?;
817                }
818            }
819        }
820
821        Ok(())
822    }
823
824    /// Check one `MoveAuthenticator` input object.
825    fn check_one_move_authenticator_object(
826        object_kind: InputObjectKind,
827        object: &Object,
828    ) -> UserInputResult {
829        match object_kind {
830            InputObjectKind::MovePackage(package_id) => {
831                return Err(UserInputError::PackageIsInMoveAuthenticatorInput { package_id });
832            }
833            InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
834                fp_ensure!(
835                    !object.is_package(),
836                    UserInputError::MovePackageAsObject {
837                        object_id: object_ref.object_id
838                    }
839                );
840                fp_ensure!(
841                    object_ref.version < Version::MAX_VALID_EXCL,
842                    UserInputError::InvalidSequenceNumber
843                );
844
845                // This is an invariant - we just load the object with the given ID and version.
846                assert_eq!(
847                    object.version(),
848                    object_ref.version,
849                    "The fetched object version {} does not match the requested version {}, object id: {}",
850                    object.version(),
851                    object_ref.version,
852                    object.id(),
853                );
854
855                // Check the digest matches - user could give a mismatched `ObjectDigest`.
856                let expected_digest = object.digest();
857                fp_ensure!(
858                    expected_digest == object_ref.digest,
859                    UserInputError::InvalidObjectDigest {
860                        object_id: object_ref.object_id,
861                        expected_digest
862                    }
863                );
864
865                match object.owner {
866                    Owner::Immutable => {
867                        // Nothing else to check for Immutable.
868                    }
869                    Owner::Address(_) => {
870                        return Err(UserInputError::AddressOwnedIsInMoveAuthenticatorInput {
871                            object_id: object.id(),
872                        });
873                    }
874                    Owner::Object(_) => {
875                        return Err(UserInputError::ObjectOwnedIsInMoveAuthenticatorInput {
876                            object_id: object.id(),
877                        });
878                    }
879                    Owner::Shared(_) => {
880                        // This object is a mutable shared object. However the transaction
881                        // specifies it as an owned object. This is inconsistent.
882                        return Err(UserInputError::NotSharedObject);
883                    }
884                    _ => {
885                        unimplemented!("a new Owner enum variant was added and needs to be handled")
886                    }
887                };
888            }
889            InputObjectKind::SharedMoveObject {
890                id: ObjectId::AUTHENTICATOR_STATE,
891                ..
892            } => {
893                return Err(UserInputError::InaccessibleSystemObject {
894                    object_id: ObjectId::AUTHENTICATOR_STATE,
895                });
896            }
897            InputObjectKind::SharedMoveObject {
898                id, mutable: true, ..
899            } => {
900                return Err(UserInputError::MutableSharedIsInMoveAuthenticatorInput {
901                    object_id: id,
902                });
903            }
904            InputObjectKind::SharedMoveObject {
905                initial_shared_version: input_initial_shared_version,
906                ..
907            } => {
908                fp_ensure!(
909                    object.version() < Version::MAX_VALID_EXCL,
910                    UserInputError::InvalidSequenceNumber
911                );
912
913                match object.owner {
914                    Owner::Address(_) | Owner::Object(_) | Owner::Immutable => {
915                        // When someone locks an object as shared it must be shared already.
916                        return Err(UserInputError::NotSharedObject);
917                    }
918                    Owner::Shared(actual_initial_shared_version) => {
919                        fp_ensure!(
920                            input_initial_shared_version == actual_initial_shared_version,
921                            UserInputError::SharedObjectStartingVersionMismatch
922                        )
923                    }
924                    _ => {
925                        unimplemented!("a new Owner enum variant was added and needs to be handled")
926                    }
927                }
928            }
929        };
930        Ok(())
931    }
932
933    /// Checks a shared `MoveAuthenticator` input against the rules that depend
934    /// only on how it is declared: it may not be the authenticator state
935    /// object, and it may not be declared mutable.
936    fn check_move_authenticator_shared_object_declaration(
937        object_kind: &InputObjectKind,
938    ) -> UserInputResult {
939        match object_kind {
940            InputObjectKind::SharedMoveObject {
941                id: ObjectId::AUTHENTICATOR_STATE,
942                ..
943            } => Err(UserInputError::InaccessibleSystemObject {
944                object_id: ObjectId::AUTHENTICATOR_STATE,
945            }),
946            InputObjectKind::SharedMoveObject {
947                id, mutable: true, ..
948            } => Err(UserInputError::MutableSharedIsInMoveAuthenticatorInput { object_id: *id }),
949            _ => Ok(()),
950        }
951    }
952
953    /// Create a union of two CheckedInputObjects, ensuring consistency
954    /// for objects that appear in both sets. The base_set is consumed and
955    /// returned with the union. The other_set is borrowed.
956    /// In the case of shared objects, the mutability can differ, but the
957    /// initial shared version must match. For other object kinds, they must
958    /// match exactly.
959    pub fn checked_input_objects_union(
960        base_set: CheckedInputObjects,
961        other_set: &CheckedInputObjects,
962    ) -> IotaResult<CheckedInputObjects> {
963        let mut base_set = base_set.into_inner();
964        for other_object in other_set.inner().iter() {
965            if let Some(base_object) = base_set.find_object_id_mut(other_object.id()) {
966                // This is an invariant
967                assert_eq!(
968                    base_object.object, other_object.object,
969                    "The object read result for input objects with the same id must be equal"
970                );
971
972                // In the case of an alive object, check that the object kind matches exactly,
973                // or that, if it is a shared object, only the mutability changes
974                if let ObjectReadResultKind::Object(_) = &other_object.object {
975                    base_object
976                        .input_object_kind
977                        .left_union_with_checks(&other_object.input_object_kind)?;
978                }
979            } else {
980                base_set.push(other_object.clone());
981            }
982        }
983        Ok(base_set.into_checked())
984    }
985
986    /// Check package verification timeout
987    #[instrument(level = "trace", skip_all)]
988    pub fn check_non_system_packages_to_be_published(
989        transaction: &Transaction,
990        protocol_config: &ProtocolConfig,
991        metrics: &Arc<BytecodeVerifierMetrics>,
992        verifier_limits_source: VerifierLimitsSource<'_>,
993    ) -> UserInputResult<()> {
994        // Only meter non-system programmable transaction blocks
995        if transaction.is_system_tx() {
996            return Ok(());
997        }
998
999        let TransactionKind::Programmable(pt) = transaction.kind() else {
1000            return Ok(());
1001        };
1002
1003        // Use the same verifier and meter for all packages.
1004        let (signing_limits, meter_config) = match verifier_limits_source {
1005            VerifierLimitsSource::NodeConfig(config) => (
1006                config.limits_for_signing(),
1007                config.meter_config_for_signing(),
1008            ),
1009            VerifierLimitsSource::ProtocolConfig => (
1010                protocol_config.verifier_signing_limits(),
1011                protocol_config.meter_config(),
1012            ),
1013        };
1014        let mut verifier = iota_execution::verifier(protocol_config, Some(signing_limits), metrics);
1015        let mut meter = verifier.meter(meter_config);
1016
1017        // Measure time for verifying all packages in the PTB
1018        let shared_meter_verifier_timer = metrics
1019            .verifier_runtime_per_ptb_success_latency
1020            .start_timer();
1021
1022        let verifier_status = pt
1023            .non_system_packages_to_be_published()
1024            .try_for_each(|module_bytes| {
1025                verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
1026            })
1027            .map_err(|e| UserInputError::PackageVerificationTimedout { err: e.to_string() });
1028
1029        match verifier_status {
1030            Ok(_) => {
1031                // Success: stop and record the success timer
1032                shared_meter_verifier_timer.stop_and_record();
1033            }
1034            Err(err) => {
1035                // Failure: redirect the success timers output to the failure timer and
1036                // discard the success timer
1037                metrics
1038                    .verifier_runtime_per_ptb_timeout_latency
1039                    .observe(shared_meter_verifier_timer.stop_and_discard());
1040                return Err(err);
1041            }
1042        };
1043
1044        Ok(())
1045    }
1046}