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