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