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                fp_ensure!(
832                    object.data.as_opt_package().is_some(),
833                    UserInputError::MoveObjectAsPackage {
834                        object_id: package_id
835                    }
836                );
837            }
838            InputObjectKind::ImmOrOwnedMoveObject(object_ref) => {
839                fp_ensure!(
840                    !object.is_package(),
841                    UserInputError::MovePackageAsObject {
842                        object_id: object_ref.object_id
843                    }
844                );
845                fp_ensure!(
846                    object_ref.version < Version::MAX_VALID_EXCL,
847                    UserInputError::InvalidSequenceNumber
848                );
849
850                // This is an invariant - we just load the object with the given ID and version.
851                assert_eq!(
852                    object.version(),
853                    object_ref.version,
854                    "The fetched object version {} does not match the requested version {}, object id: {}",
855                    object.version(),
856                    object_ref.version,
857                    object.id(),
858                );
859
860                // Check the digest matches - user could give a mismatched `ObjectDigest`.
861                let expected_digest = object.digest();
862                fp_ensure!(
863                    expected_digest == object_ref.digest,
864                    UserInputError::InvalidObjectDigest {
865                        object_id: object_ref.object_id,
866                        expected_digest
867                    }
868                );
869
870                match object.owner {
871                    Owner::Immutable => {
872                        // Nothing else to check for Immutable.
873                    }
874                    Owner::Address(_) => {
875                        return Err(UserInputError::AddressOwnedIsInMoveAuthenticatorInput {
876                            object_id: object.id(),
877                        });
878                    }
879                    Owner::Object(_) => {
880                        return Err(UserInputError::ObjectOwnedIsInMoveAuthenticatorInput {
881                            object_id: object.id(),
882                        });
883                    }
884                    Owner::Shared(_) => {
885                        // This object is a mutable shared object. However the transaction
886                        // specifies it as an owned object. This is inconsistent.
887                        return Err(UserInputError::NotSharedObject);
888                    }
889                    _ => {
890                        unimplemented!("a new Owner enum variant was added and needs to be handled")
891                    }
892                };
893            }
894            InputObjectKind::SharedMoveObject {
895                id: ObjectId::AUTHENTICATOR_STATE,
896                ..
897            } => {
898                return Err(UserInputError::InaccessibleSystemObject {
899                    object_id: ObjectId::AUTHENTICATOR_STATE,
900                });
901            }
902            InputObjectKind::SharedMoveObject {
903                id, mutable: true, ..
904            } => {
905                return Err(UserInputError::MutableSharedIsInMoveAuthenticatorInput {
906                    object_id: id,
907                });
908            }
909            InputObjectKind::SharedMoveObject {
910                initial_shared_version: input_initial_shared_version,
911                ..
912            } => {
913                fp_ensure!(
914                    object.version() < Version::MAX_VALID_EXCL,
915                    UserInputError::InvalidSequenceNumber
916                );
917
918                match object.owner {
919                    Owner::Address(_) | Owner::Object(_) | Owner::Immutable => {
920                        // When someone locks an object as shared it must be shared already.
921                        return Err(UserInputError::NotSharedObject);
922                    }
923                    Owner::Shared(actual_initial_shared_version) => {
924                        fp_ensure!(
925                            input_initial_shared_version == actual_initial_shared_version,
926                            UserInputError::SharedObjectStartingVersionMismatch
927                        )
928                    }
929                    _ => {
930                        unimplemented!("a new Owner enum variant was added and needs to be handled")
931                    }
932                }
933            }
934        };
935        Ok(())
936    }
937
938    /// Checks a shared `MoveAuthenticator` input against the rules that depend
939    /// only on how it is declared: it may not be the authenticator state
940    /// object, and it may not be declared mutable.
941    fn check_move_authenticator_shared_object_declaration(
942        object_kind: &InputObjectKind,
943    ) -> UserInputResult {
944        match object_kind {
945            InputObjectKind::SharedMoveObject {
946                id: ObjectId::AUTHENTICATOR_STATE,
947                ..
948            } => Err(UserInputError::InaccessibleSystemObject {
949                object_id: ObjectId::AUTHENTICATOR_STATE,
950            }),
951            InputObjectKind::SharedMoveObject {
952                id, mutable: true, ..
953            } => Err(UserInputError::MutableSharedIsInMoveAuthenticatorInput { object_id: *id }),
954            _ => Ok(()),
955        }
956    }
957
958    /// Create a union of two CheckedInputObjects, ensuring consistency
959    /// for objects that appear in both sets. The base_set is consumed and
960    /// returned with the union. The other_set is borrowed.
961    /// In the case of shared objects, the mutability can differ, but the
962    /// initial shared version must match. For other object kinds, they must
963    /// match exactly.
964    pub fn checked_input_objects_union(
965        base_set: CheckedInputObjects,
966        other_set: &CheckedInputObjects,
967    ) -> IotaResult<CheckedInputObjects> {
968        let mut base_set = base_set.into_inner();
969        for other_object in other_set.inner().iter() {
970            if let Some(base_object) = base_set.find_object_id_mut(other_object.id()) {
971                // This is an invariant
972                assert_eq!(
973                    base_object.object, other_object.object,
974                    "The object read result for input objects with the same id must be equal"
975                );
976
977                // In the case of an alive object, check that the object kind matches exactly,
978                // or that, if it is a shared object, only the mutability changes
979                if let ObjectReadResultKind::Object(_) = &other_object.object {
980                    base_object
981                        .input_object_kind
982                        .left_union_with_checks(&other_object.input_object_kind)?;
983                }
984            } else {
985                base_set.push(other_object.clone());
986            }
987        }
988        Ok(base_set.into_checked())
989    }
990
991    /// Check package verification timeout
992    #[instrument(level = "trace", skip_all)]
993    pub fn check_non_system_packages_to_be_published(
994        transaction: &Transaction,
995        protocol_config: &ProtocolConfig,
996        metrics: &Arc<BytecodeVerifierMetrics>,
997        verifier_limits_source: VerifierLimitsSource<'_>,
998    ) -> UserInputResult<()> {
999        // Only meter non-system programmable transaction blocks
1000        if transaction.is_system_tx() {
1001            return Ok(());
1002        }
1003
1004        let TransactionKind::Programmable(pt) = transaction.kind() else {
1005            return Ok(());
1006        };
1007
1008        // Use the same verifier and meter for all packages.
1009        let (verifier_config, meter_config) = match verifier_limits_source {
1010            VerifierLimitsSource::NodeConfig(config) => (
1011                protocol_config.verifier_config(Some(config.limits_for_signing())),
1012                config.meter_config_for_signing(),
1013            ),
1014            VerifierLimitsSource::ProtocolConfig => (
1015                protocol_config.post_consensus_verifier_config(),
1016                protocol_config.meter_config(),
1017            ),
1018        };
1019        let mut verifier = iota_execution::verifier(protocol_config, verifier_config, metrics);
1020        let mut meter = verifier.meter(meter_config);
1021
1022        // Measure time for verifying all packages in the PTB
1023        let shared_meter_verifier_timer = metrics
1024            .verifier_runtime_per_ptb_success_latency
1025            .start_timer();
1026
1027        let verifier_status = pt
1028            .non_system_packages_to_be_published()
1029            .try_for_each(|module_bytes| {
1030                verifier.meter_module_bytes(protocol_config, module_bytes, meter.as_mut())
1031            })
1032            .map_err(|e| UserInputError::PackageVerificationTimedout { err: e.to_string() });
1033
1034        match verifier_status {
1035            Ok(_) => {
1036                // Success: stop and record the success timer
1037                shared_meter_verifier_timer.stop_and_record();
1038            }
1039            Err(err) => {
1040                // Failure: redirect the success timers output to the failure timer and
1041                // discard the success timer
1042                metrics
1043                    .verifier_runtime_per_ptb_timeout_latency
1044                    .observe(shared_meter_verifier_timer.stop_and_discard());
1045                return Err(err);
1046            }
1047        };
1048
1049        Ok(())
1050    }
1051
1052    #[cfg(test)]
1053    mod tests {
1054        use iota_sdk_types::{MovePackage, TransactionDigest};
1055
1056        use super::*;
1057
1058        fn package(id: ObjectId) -> Object {
1059            Object::new_from_package(
1060                MovePackage::new(
1061                    id,
1062                    Version::OBJECT_START,
1063                    BTreeMap::new(),
1064                    vec![],
1065                    BTreeMap::new(),
1066                ),
1067                TransactionDigest::genesis_marker(),
1068            )
1069        }
1070
1071        /// `object`, read for the kind `MovePackage(id)`.
1072        fn read_as_package(id: ObjectId, object: Object) -> InputObjects {
1073            InputObjects::new(vec![ObjectReadResult::new(
1074                InputObjectKind::MovePackage(id),
1075                object.into(),
1076            )])
1077        }
1078
1079        #[test]
1080        fn authenticator_check_accepts_its_type_arg_package() {
1081            let id = ObjectId::random();
1082            check_move_authenticator_objects(&read_as_package(id, package(id))).unwrap();
1083        }
1084
1085        #[test]
1086        fn authenticator_check_rejects_an_object_named_as_a_type_arg_package() {
1087            let id = ObjectId::random();
1088            assert_eq!(
1089                check_move_authenticator_objects(&read_as_package(
1090                    id,
1091                    Object::immutable_with_id_for_testing(id)
1092                ))
1093                .unwrap_err(),
1094                UserInputError::MoveObjectAsPackage { object_id: id }
1095            );
1096        }
1097    }
1098}