Skip to main content

iota_types/
transaction.rs

1// Copyright (c) 2021, Facebook, Inc. and its affiliates
2// Copyright (c) Mysten Labs, Inc.
3// Modifications Copyright (c) 2024 IOTA Stiftung
4// SPDX-License-Identifier: Apache-2.0
5
6// zkLogin/AuthenticatorStateUpdate types are kept (deprecated) for
7// serialization compatibility only.
8
9use std::{
10    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
11    fmt::{Debug, Display, Formatter, Write},
12    hash::Hash,
13    iter::{self},
14};
15
16use anyhow::bail;
17use fastcrypto::{encoding::Base64, hash::HashFunction};
18use iota_protocol_config::ProtocolConfig;
19use iota_sdk_types::{
20    Argument, CancelledTransaction, Command, ConsensusCommitPrologueV1,
21    ConsensusDeterminedVersionAssignments, Digest, Identifier, Input, MakeMoveVector, MergeCoins,
22    MoveCall, ObjectId, Owner, Publish, SplitCoins, TransferObjects, TypeTag, Upgrade,
23    crypto::{Intent, IntentMessage, IntentScope},
24};
25pub use iota_sdk_types::{
26    EndOfEpochTransactionKind, GasPayment as GasData, GenesisObject, GenesisTransaction,
27    ProgrammableTransaction, RandomnessStateUpdate, SharedObjectReference as SharedObjectRef,
28    SystemPackage, Transaction as TransactionData, TransactionExpiration, TransactionKind,
29    TransactionV1 as TransactionDataV1,
30};
31use itertools::Either;
32use nonempty::{NonEmpty, nonempty};
33use serde::{Deserialize, Serialize};
34use tap::Pipe;
35use tracing::{instrument, trace};
36
37use super::{base_types::*, error::*};
38use crate::{
39    IOTA_CLOCK_OBJECT_SHARED_VERSION, IOTA_SYSTEM_STATE_OBJECT_SHARED_VERSION,
40    committee::{Committee, EpochId},
41    crypto::{
42        AuthoritySignInfo, AuthoritySignInfoTrait, AuthoritySignature,
43        AuthorityStrongQuorumSignInfo, DefaultHash, Ed25519IotaSignature, EmptySignInfo,
44        IotaSignatureInner, RandomnessRound, Signature, Signer, ToFromBytes,
45    },
46    digests::{CertificateDigest, ConsensusCommitDigest, SenderSignedDataDigest},
47    event::Event,
48    execution::SharedInput,
49    message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope},
50    messages_checkpoint::CheckpointTimestamp,
51    move_authenticator::MoveAuthenticator,
52    object::{MoveObject, Object},
53    programmable_transaction_builder::ProgrammableTransactionBuilder,
54    signature::{GenericSignature, VerifyParams},
55    signature_verification::verify_sender_signed_data_message_signatures,
56};
57
58pub const TEST_ONLY_GAS_UNIT_FOR_TRANSFER: u64 = 10_000;
59pub const TEST_ONLY_GAS_UNIT_FOR_OBJECT_BASICS: u64 = 50_000;
60pub const TEST_ONLY_GAS_UNIT_FOR_PUBLISH: u64 = 50_000;
61pub const TEST_ONLY_GAS_UNIT_FOR_STAKING: u64 = 50_000;
62pub const TEST_ONLY_GAS_UNIT_FOR_GENERIC: u64 = 50_000;
63pub const TEST_ONLY_GAS_UNIT_FOR_SPLIT_COIN: u64 = 10_000;
64// For some transactions we may either perform heavy operations or touch
65// objects that are storage expensive. That may happen (and often is the case)
66// because the object touched are set up in genesis and carry no storage cost
67// (and thus rebate) on first usage.
68pub const TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE: u64 = 5_000_000;
69
70pub const GAS_PRICE_FOR_SYSTEM_TX: u64 = 1;
71
72pub const DEFAULT_VALIDATOR_GAS_PRICE: u64 = 1000;
73
74const BLOCKED_MOVE_FUNCTIONS: [(ObjectId, &str, &str); 0] = [];
75
76#[cfg(test)]
77#[path = "unit_tests/messages_tests.rs"]
78mod messages_tests;
79
80/// Type alias for the SDK's `Input` type, used as transaction call arguments.
81pub type CallArg = Input;
82
83pub fn type_tag_validity_check(
84    tag: &TypeTag,
85    config: &ProtocolConfig,
86    starting_count: &mut usize,
87) -> UserInputResult<()> {
88    let mut stack = vec![(tag, 1)];
89    while let Some((tag, depth)) = stack.pop() {
90        *starting_count += 1;
91        fp_ensure!(
92            *starting_count < config.max_type_arguments() as usize,
93            UserInputError::SizeLimitExceeded {
94                limit: "maximum type arguments in a call transaction".to_string(),
95                value: config.max_type_arguments().to_string()
96            }
97        );
98        fp_ensure!(
99            depth < config.max_type_argument_depth(),
100            UserInputError::SizeLimitExceeded {
101                limit: "maximum type argument depth in a call transaction".to_string(),
102                value: config.max_type_argument_depth().to_string()
103            }
104        );
105        match tag {
106            TypeTag::Bool
107            | TypeTag::U8
108            | TypeTag::U64
109            | TypeTag::U128
110            | TypeTag::Address
111            | TypeTag::Signer
112            | TypeTag::U16
113            | TypeTag::U32
114            | TypeTag::U256 => (),
115            TypeTag::Vector(t) => {
116                stack.push((t, depth + 1));
117            }
118            TypeTag::Struct(s) => {
119                let next_depth = depth + 1;
120                if config.validate_identifier_inputs() {
121                    fp_ensure!(
122                        Identifier::is_valid(s.module().as_str()),
123                        UserInputError::InvalidIdentifier {
124                            error: s.module().as_str().to_owned()
125                        }
126                    );
127                    fp_ensure!(
128                        Identifier::is_valid(s.name().as_str()),
129                        UserInputError::InvalidIdentifier {
130                            error: s.name().as_str().to_owned()
131                        }
132                    );
133                }
134                stack.extend(s.type_params().iter().map(|t| (t, next_depth)));
135            }
136        }
137    }
138    Ok(())
139}
140
141/// Extension trait for [`EndOfEpochTransactionKind`] that adds methods
142/// requiring iota-types-specific types (like [`InputObjectKind`] and
143/// [`ProtocolConfig`]) that are not available in the SDK.
144pub(crate) trait EndOfEpochTransactionKindExt {
145    fn input_objects(&self) -> Vec<InputObjectKind>;
146    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
147}
148
149impl EndOfEpochTransactionKindExt for EndOfEpochTransactionKind {
150    fn input_objects(&self) -> Vec<InputObjectKind> {
151        match self {
152            Self::ChangeEpoch(_)
153            | Self::ChangeEpochV2(_)
154            | Self::ChangeEpochV3(_)
155            | Self::ChangeEpochV4(_) => {
156                vec![InputObjectKind::SharedMoveObject {
157                    id: ObjectId::SYSTEM_STATE,
158                    initial_shared_version: IOTA_SYSTEM_STATE_OBJECT_SHARED_VERSION,
159                    mutable: true,
160                }]
161            }
162            _ => unimplemented!(
163                "a new EndOfEpochTransactionKind enum variant was added and needs to be handled"
164            ),
165        }
166    }
167
168    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
169        match self {
170            Self::ChangeEpoch(_) => {
171                if config.protocol_defined_base_fee() {
172                    return Err(UserInputError::Unsupported(
173                        "protocol defined base fee not supported".to_string(),
174                    ));
175                }
176                if config.select_committee_from_eligible_validators() {
177                    return Err(UserInputError::Unsupported(
178                        "selecting committee only among validators supporting the protocol version not supported".to_string(),
179                    ));
180                }
181                if config.pass_validator_scores_to_advance_epoch() {
182                    return Err(UserInputError::Unsupported(
183                        "passing of validator scores not supported".to_string(),
184                    ));
185                }
186                if config.adjust_rewards_by_score() {
187                    return Err(UserInputError::Unsupported(
188                        "adjusting rewards by score not supported".to_string(),
189                    ));
190                }
191            }
192            Self::ChangeEpochV2(_) => {
193                if !config.protocol_defined_base_fee() {
194                    return Err(UserInputError::Unsupported(
195                        "protocol defined base fee required".to_string(),
196                    ));
197                }
198                if config.select_committee_from_eligible_validators() {
199                    return Err(UserInputError::Unsupported(
200                        "selecting committee only among validators supporting the protocol version not supported".to_string(),
201                    ));
202                }
203                if config.pass_validator_scores_to_advance_epoch() {
204                    return Err(UserInputError::Unsupported(
205                        "passing of validator scores not supported".to_string(),
206                    ));
207                }
208                if config.adjust_rewards_by_score() {
209                    return Err(UserInputError::Unsupported(
210                        "adjusting rewards by score not supported".to_string(),
211                    ));
212                }
213            }
214            Self::ChangeEpochV3(_) => {
215                if !config.protocol_defined_base_fee() {
216                    return Err(UserInputError::Unsupported(
217                        "protocol defined base fee required".to_string(),
218                    ));
219                }
220                if !config.select_committee_from_eligible_validators() {
221                    return Err(UserInputError::Unsupported(
222                        "selecting committee only among validators supporting the protocol version required".to_string(),
223                    ));
224                }
225                if config.pass_validator_scores_to_advance_epoch() {
226                    return Err(UserInputError::Unsupported(
227                        "passing of validator scores not supported".to_string(),
228                    ));
229                }
230                if config.adjust_rewards_by_score() {
231                    return Err(UserInputError::Unsupported(
232                        "adjusting rewards by score not supported".to_string(),
233                    ));
234                }
235            }
236            Self::ChangeEpochV4(_) => {
237                if !config.protocol_defined_base_fee() {
238                    return Err(UserInputError::Unsupported(
239                        "protocol defined base fee required".to_string(),
240                    ));
241                }
242                if !config.select_committee_from_eligible_validators() {
243                    return Err(UserInputError::Unsupported(
244                        "selecting committee only among validators supporting the protocol version required".to_string(),
245                    ));
246                }
247                if !config.pass_validator_scores_to_advance_epoch() {
248                    return Err(UserInputError::Unsupported(
249                        "passing of validator scores required".to_string(),
250                    ));
251                }
252            }
253            _ => unimplemented!(
254                "a new EndOfEpochTransactionKind enum variant was added and needs to be handled"
255            ),
256        }
257        Ok(())
258    }
259}
260
261mod call_arg_ext {
262    pub trait Sealed {}
263    impl Sealed for super::CallArg {}
264}
265
266/// Extension trait for [`CallArg`] providing helper methods.
267pub trait CallArgExt: Sized + call_arg_ext::Sealed {
268    /// Returns the input object kind for this argument, excluding receiving
269    /// objects.
270    fn input_object_kind(&self) -> Option<InputObjectKind>;
271
272    /// Validity check for this argument against the given protocol config.
273    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
274}
275
276impl CallArgExt for CallArg {
277    fn input_object_kind(&self) -> Option<InputObjectKind> {
278        match self {
279            CallArg::ImmutableOrOwned(object_ref) => {
280                Some(InputObjectKind::ImmOrOwnedMoveObject(*object_ref))
281            }
282            CallArg::Shared(SharedObjectRef {
283                object_id,
284                initial_shared_version,
285                mutable,
286            }) => Some(InputObjectKind::SharedMoveObject {
287                id: *object_id,
288                initial_shared_version: *initial_shared_version,
289                mutable: *mutable,
290            }),
291            CallArg::Pure(_) | CallArg::Receiving(_) => None,
292            _ => unimplemented!("a new CallArg enum variant was added and needs to be handled"),
293        }
294    }
295
296    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
297        match self {
298            CallArg::Pure(bytes) => {
299                fp_ensure!(
300                    bytes.len() < config.max_pure_argument_size() as usize,
301                    UserInputError::SizeLimitExceeded {
302                        limit: "maximum pure argument size".to_string(),
303                        value: config.max_pure_argument_size().to_string()
304                    }
305                );
306            }
307            CallArg::ImmutableOrOwned(_) | CallArg::Shared(_) | CallArg::Receiving(_) => {
308                // No validation needed for these variants
309            }
310            _ => unimplemented!("a new CallArg enum variant was added and needs to be handled"),
311        }
312        Ok(())
313    }
314}
315
316// Add package IDs, `ObjectId`, for types defined in modules.
317fn add_type_tag_packages(packages: &mut BTreeSet<ObjectId>, type_argument: &TypeTag) {
318    let mut stack = vec![type_argument];
319    while let Some(cur) = stack.pop() {
320        match cur {
321            TypeTag::U8
322            | TypeTag::U16
323            | TypeTag::U32
324            | TypeTag::U64
325            | TypeTag::U128
326            | TypeTag::U256
327            | TypeTag::Bool
328            | TypeTag::Address
329            | TypeTag::Signer => (),
330            TypeTag::Vector(inner) => stack.push(inner),
331            TypeTag::Struct(struct_tag) => {
332                packages.insert(ObjectId::new(struct_tag.address().into_bytes()));
333                stack.extend(struct_tag.type_params().iter())
334            }
335        }
336    }
337}
338
339mod move_call_ext {
340    pub trait Sealed {}
341    impl Sealed for super::MoveCall {}
342}
343
344pub trait MoveCallExt: Sized + move_call_ext::Sealed {
345    fn input_objects(&self) -> Vec<InputObjectKind>;
346    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
347    fn is_input_arg_used(&self, arg: u16) -> bool;
348}
349
350impl MoveCallExt for MoveCall {
351    fn input_objects(&self) -> Vec<InputObjectKind> {
352        let mut packages = BTreeSet::from([self.package]);
353        for type_argument in &self.type_arguments {
354            add_type_tag_packages(&mut packages, type_argument);
355        }
356        packages
357            .into_iter()
358            .map(InputObjectKind::MovePackage)
359            .collect()
360    }
361
362    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
363        let is_blocked = BLOCKED_MOVE_FUNCTIONS.contains(&(
364            self.package,
365            self.module.as_str(),
366            self.function.as_str(),
367        ));
368        fp_ensure!(!is_blocked, UserInputError::BlockedMoveFunction);
369        let mut type_arguments_count = 0;
370        for tag in &self.type_arguments {
371            type_tag_validity_check(tag, config, &mut type_arguments_count)?;
372        }
373        fp_ensure!(
374            self.arguments.len() < config.max_arguments() as usize,
375            UserInputError::SizeLimitExceeded {
376                limit: "maximum arguments in a move call".to_string(),
377                value: config.max_arguments().to_string()
378            }
379        );
380        if config.validate_identifier_inputs() {
381            fp_ensure!(
382                Identifier::is_valid(&self.module),
383                UserInputError::InvalidIdentifier {
384                    error: self.module.to_string()
385                }
386            );
387            fp_ensure!(
388                Identifier::is_valid(&self.function),
389                UserInputError::InvalidIdentifier {
390                    error: self.function.to_string()
391                }
392            );
393        }
394        Ok(())
395    }
396
397    fn is_input_arg_used(&self, arg: u16) -> bool {
398        self.arguments
399            .iter()
400            .any(|a| matches!(a, Argument::Input(inp) if *inp == arg))
401    }
402}
403
404mod command_ext {
405    pub trait Sealed {}
406    impl Sealed for super::Command {}
407}
408
409pub trait CommandExt: Sized + command_ext::Sealed {
410    fn input_objects(&self) -> Vec<InputObjectKind>;
411    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
412    fn non_system_packages_to_be_published(&self) -> Option<&Vec<Vec<u8>>>;
413    fn is_input_arg_used(&self, input_arg: u16) -> bool;
414}
415
416impl CommandExt for Command {
417    fn input_objects(&self) -> Vec<InputObjectKind> {
418        match self {
419            Command::MoveCall(cmd) => cmd.input_objects(),
420            Command::Upgrade(cmd) => cmd
421                .dependencies
422                .iter()
423                .map(|id| InputObjectKind::MovePackage(*id))
424                .chain(Some(InputObjectKind::MovePackage(cmd.package)))
425                .collect(),
426            Command::Publish(cmd) => cmd
427                .dependencies
428                .iter()
429                .map(|id| InputObjectKind::MovePackage(*id))
430                .collect(),
431            Command::MakeMoveVector(MakeMoveVector { type_: Some(t), .. }) => {
432                let mut packages = BTreeSet::new();
433                add_type_tag_packages(&mut packages, t);
434                packages
435                    .into_iter()
436                    .map(InputObjectKind::MovePackage)
437                    .collect()
438            }
439            Command::MakeMoveVector(MakeMoveVector { type_: None, .. })
440            | Command::TransferObjects(_)
441            | Command::SplitCoins(_)
442            | Command::MergeCoins(_) => vec![],
443            _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
444        }
445    }
446
447    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
448        match self {
449            Command::MoveCall(call) => call.validity_check(config)?,
450            Command::TransferObjects(TransferObjects { objects: args, .. })
451            | Command::MergeCoins(MergeCoins {
452                coins_to_merge: args,
453                ..
454            })
455            | Command::SplitCoins(SplitCoins { amounts: args, .. }) => {
456                fp_ensure!(!args.is_empty(), UserInputError::EmptyCommandInput);
457                fp_ensure!(
458                    args.len() < config.max_arguments() as usize,
459                    UserInputError::SizeLimitExceeded {
460                        limit: "maximum arguments in a programmable transaction command"
461                            .to_string(),
462                        value: config.max_arguments().to_string()
463                    }
464                );
465            }
466            Command::MakeMoveVector(MakeMoveVector {
467                type_: ty_opt,
468                elements: args,
469            }) => {
470                // ty_opt.is_none() ==> !args.is_empty()
471                fp_ensure!(
472                    ty_opt.is_some() || !args.is_empty(),
473                    UserInputError::EmptyCommandInput
474                );
475                if let Some(ty) = ty_opt {
476                    let mut type_arguments_count = 0;
477                    type_tag_validity_check(ty, config, &mut type_arguments_count)?;
478                }
479                fp_ensure!(
480                    args.len() < config.max_arguments() as usize,
481                    UserInputError::SizeLimitExceeded {
482                        limit: "maximum arguments in a programmable transaction command"
483                            .to_string(),
484                        value: config.max_arguments().to_string()
485                    }
486                );
487            }
488            Command::Publish(Publish {
489                modules,
490                dependencies,
491            })
492            | Command::Upgrade(Upgrade {
493                modules,
494                dependencies,
495                ..
496            }) => {
497                fp_ensure!(!modules.is_empty(), UserInputError::EmptyCommandInput);
498                fp_ensure!(
499                    modules.len() < config.max_modules_in_publish() as usize,
500                    UserInputError::SizeLimitExceeded {
501                        limit: "maximum modules in a programmable transaction upgrade command"
502                            .to_string(),
503                        value: config.max_modules_in_publish().to_string()
504                    }
505                );
506                if let Some(max_package_dependencies) = config.max_package_dependencies_as_option()
507                {
508                    fp_ensure!(
509                        dependencies.len() < max_package_dependencies as usize,
510                        UserInputError::SizeLimitExceeded {
511                            limit: "maximum package dependencies".to_string(),
512                            value: max_package_dependencies.to_string()
513                        }
514                    );
515                };
516            }
517            _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
518        };
519
520        Ok(())
521    }
522
523    fn non_system_packages_to_be_published(&self) -> Option<&Vec<Vec<u8>>> {
524        match self {
525            Command::Publish(cmd) => Some(&cmd.modules),
526            Command::Upgrade(cmd) => Some(&cmd.modules),
527            Command::MoveCall(_)
528            | Command::TransferObjects(_)
529            | Command::SplitCoins(_)
530            | Command::MergeCoins(_)
531            | Command::MakeMoveVector(_) => None,
532            _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
533        }
534    }
535
536    fn is_input_arg_used(&self, input_arg: u16) -> bool {
537        match self {
538            Command::MoveCall(c) => c.is_input_arg_used(input_arg),
539            Command::TransferObjects(TransferObjects {
540                objects: args,
541                address: arg,
542            })
543            | Command::MergeCoins(MergeCoins {
544                coins_to_merge: args,
545                coin: arg,
546            })
547            | Command::SplitCoins(SplitCoins {
548                amounts: args,
549                coin: arg,
550            }) => args
551                .iter()
552                .chain(iter::once(arg))
553                .any(|arg| matches!(arg, Argument::Input(input) if *input == input_arg)),
554            Command::MakeMoveVector(MakeMoveVector { elements, .. }) => elements
555                .iter()
556                .any(|arg| matches!(arg, Argument::Input(input) if *input == input_arg)),
557            Command::Upgrade(Upgrade { ticket, .. }) => {
558                matches!(ticket, Argument::Input(input) if *input == input_arg)
559            }
560            Command::Publish(_) => false,
561            _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
562        }
563    }
564}
565
566mod programmable_transaction_ext {
567    pub trait Sealed {}
568    impl Sealed for super::ProgrammableTransaction {}
569}
570
571pub trait ProgrammableTransactionExt: Sized + programmable_transaction_ext::Sealed {
572    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
573    fn receiving_objects(&self) -> Vec<ObjectRef>;
574    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
575    fn shared_input_objects(&self) -> impl Iterator<Item = SharedObjectRef>;
576    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)>;
577    fn non_system_packages_to_be_published(&self) -> impl Iterator<Item = &Vec<Vec<u8>>>;
578}
579
580impl ProgrammableTransactionExt for ProgrammableTransaction {
581    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
582        let ProgrammableTransaction { inputs, commands } = self;
583        let input_arg_objects = inputs
584            .iter()
585            .filter_map(|arg| arg.input_object_kind())
586            .collect::<Vec<_>>();
587        // all objects, not just mutable, must be unique
588        let mut used = HashSet::new();
589        if !input_arg_objects.iter().all(|o| used.insert(o.object_id())) {
590            return Err(UserInputError::DuplicateObjectRefInput);
591        }
592        // do not duplicate packages referred to in commands
593        let command_input_objects: BTreeSet<InputObjectKind> = commands
594            .iter()
595            .flat_map(|command| command.input_objects())
596            .collect();
597        Ok(input_arg_objects
598            .into_iter()
599            .chain(command_input_objects)
600            .collect())
601    }
602
603    fn receiving_objects(&self) -> Vec<ObjectRef> {
604        let ProgrammableTransaction { inputs, .. } = self;
605        inputs
606            .iter()
607            .filter_map(|arg| arg.as_receiving_opt().copied())
608            .collect()
609    }
610
611    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
612        let ProgrammableTransaction { inputs, commands } = self;
613        fp_ensure!(
614            commands.len() < config.max_programmable_tx_commands() as usize,
615            UserInputError::SizeLimitExceeded {
616                limit: "maximum commands in a programmable transaction".to_string(),
617                value: config.max_programmable_tx_commands().to_string()
618            }
619        );
620        let total_inputs = self.input_objects()?.len() + self.receiving_objects().len();
621        fp_ensure!(
622            total_inputs <= config.max_input_objects() as usize,
623            UserInputError::SizeLimitExceeded {
624                limit: "maximum input + receiving objects in a transaction".to_string(),
625                value: config.max_input_objects().to_string()
626            }
627        );
628        for input in inputs {
629            input.validity_check(config)?
630        }
631        if let Some(max_publish_commands) = config.max_publish_or_upgrade_per_ptb_as_option() {
632            let publish_count = commands
633                .iter()
634                .filter(|c| c.is_publish() || c.is_upgrade())
635                .count() as u64;
636            fp_ensure!(
637                publish_count <= max_publish_commands,
638                UserInputError::MaxPublishCountExceeded {
639                    max_publish_commands,
640                    publish_count,
641                }
642            );
643        }
644        for command in commands {
645            command.validity_check(config)?;
646        }
647
648        // If randomness is used, it must be enabled by protocol config.
649        // A command that uses Random can only be followed by TransferObjects or
650        // MergeCoins.
651        if let Some(random_index) = inputs.iter().position(|obj| {
652            matches!(obj, CallArg::Shared(SharedObjectRef { object_id, .. }) if *object_id == ObjectId::RANDOMNESS_STATE)
653        }) {
654            let mut used_random_object = false;
655            let random_index = random_index.try_into().unwrap();
656            for command in commands {
657                if !used_random_object {
658                    used_random_object = command.is_input_arg_used(random_index);
659                } else {
660                    fp_ensure!(
661                        command.is_transfer_objects() || command.is_merge_coins(),
662                        UserInputError::PostRandomCommandRestrictions
663                    );
664                }
665            }
666        }
667
668        Ok(())
669    }
670
671    fn shared_input_objects(&self) -> impl Iterator<Item = SharedObjectRef> {
672        self.inputs.iter().filter_map(|arg| match arg {
673            CallArg::Shared(shared) => Some(*shared),
674            CallArg::Pure(_) | CallArg::Receiving(_) | CallArg::ImmutableOrOwned(_) => None,
675            _ => unimplemented!("a new CallArg enum variant was added and needs to be handled"),
676        })
677    }
678
679    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)> {
680        self.commands
681            .iter()
682            .filter_map(|command| match command {
683                Command::MoveCall(m) => Some((&m.package, m.module.as_str(), m.function.as_str())),
684                _ => None,
685            })
686            .collect()
687    }
688
689    fn non_system_packages_to_be_published(&self) -> impl Iterator<Item = &Vec<Vec<u8>>> {
690        self.commands
691            .iter()
692            .filter_map(|q| q.non_system_packages_to_be_published())
693    }
694}
695
696/// Merges `other` into `this` shared input object.
697/// If there is a conflict in mutability, the resulting object will be
698/// mutable. Errors if the id or initial_shared_version do not match.
699fn left_union_shared_input_objects(
700    this: &mut SharedObjectRef,
701    other: &SharedObjectRef,
702) -> UserInputResult<()> {
703    fp_ensure!(
704        this.object_id == other.object_id,
705        UserInputError::SharedObjectIdMismatch
706    );
707    fp_ensure!(
708        this.initial_shared_version == other.initial_shared_version,
709        UserInputError::SharedObjectStartingVersionMismatch
710    );
711
712    if !this.mutable && other.mutable {
713        this.mutable = other.mutable;
714    }
715
716    Ok(())
717}
718
719mod transaction_kind_ext {
720    pub trait Sealed {}
721    impl Sealed for super::TransactionKind {}
722}
723
724pub trait TransactionKindExt: Sized + transaction_kind_ext::Sealed {
725    /// If this is an advance epoch transaction, returns (total gas charged,
726    /// total gas rebated). TODO: We should use `GasCostSummary` directly in
727    /// `ChangeEpoch` struct, and return that directly.
728    fn get_advance_epoch_tx_gas_summary(&self) -> Option<(u64, u64)>;
729    /// Returns `true` if the transaction contains at least one shared object.
730    fn contains_shared_object(&self) -> bool;
731    /// Returns an iterator of all shared input objects used by this
732    /// transaction.
733    fn shared_input_objects(&self) -> impl Iterator<Item = SharedObjectRef> + '_;
734    /// Returns the move calls made by this transaction as a list of
735    /// (package, module, function) tuples.
736    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)>;
737    /// Returns the objects received by this transaction.
738    fn receiving_objects(&self) -> Vec<ObjectRef>;
739    /// Return the metadata of each of the input objects for the transaction.
740    /// For a Move object, we attach the object reference;
741    /// for a Move package, we provide the object id only since they never
742    /// change on chain. TODO: use an iterator over references here instead
743    /// of a `Vec` to avoid allocations.
744    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
745    /// Validates the transaction against the given protocol config.
746    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
747    /// Returns an iterator over the commands in this transaction.
748    fn iter_commands(&self) -> impl Iterator<Item = &Command>;
749    /// Returns a human-readable name for this transaction kind.
750    fn name(&self) -> &'static str;
751}
752
753impl TransactionKindExt for TransactionKind {
754    fn get_advance_epoch_tx_gas_summary(&self) -> Option<(u64, u64)> {
755        match self {
756            Self::EndOfEpoch(txns) => {
757                match txns.last().expect("at least one end-of-epoch txn required") {
758                    EndOfEpochTransactionKind::ChangeEpoch(e) => {
759                        Some((e.computation_charge + e.storage_charge, e.storage_rebate))
760                    }
761                    EndOfEpochTransactionKind::ChangeEpochV2(e) => {
762                        Some((e.computation_charge + e.storage_charge, e.storage_rebate))
763                    }
764                    EndOfEpochTransactionKind::ChangeEpochV3(e) => {
765                        Some((e.computation_charge + e.storage_charge, e.storage_rebate))
766                    }
767                    EndOfEpochTransactionKind::ChangeEpochV4(e) => {
768                        Some((e.computation_charge + e.storage_charge, e.storage_rebate))
769                    }
770                    _ => unimplemented!(
771                        "a new EndOfEpochTransactionKind enum variant was added and needs to be handled"
772                    ),
773                }
774            }
775            _ => None,
776        }
777    }
778
779    fn contains_shared_object(&self) -> bool {
780        self.shared_input_objects().next().is_some()
781    }
782
783    fn shared_input_objects(&self) -> impl Iterator<Item = SharedObjectRef> + '_ {
784        match &self {
785            Self::ConsensusCommitPrologueV1(_) => Either::Left(Either::Left(iter::once(
786                SharedObjectRef::new(ObjectId::CLOCK, IOTA_CLOCK_OBJECT_SHARED_VERSION, true),
787            ))),
788            #[allow(deprecated)]
789            Self::AuthenticatorStateUpdateV1Deprecated => {
790                // Deprecated: Authenticator state (JWK) is deprecated and
791                // was never enabled. These transaction kinds are retained
792                // only for BCS enum variant compatibility.
793                Either::Right(Either::Right(iter::empty()))
794            }
795            Self::RandomnessStateUpdate(update) => {
796                Either::Left(Either::Left(iter::once(SharedObjectRef::new(
797                    ObjectId::RANDOMNESS_STATE,
798                    update.randomness_obj_initial_shared_version,
799                    true,
800                ))))
801            }
802            Self::EndOfEpoch(txns) => Either::Left(Either::Right(
803                txns.iter().flat_map(|txn| txn.shared_input_objects()),
804            )),
805            Self::Programmable(pt) => Either::Right(Either::Left(pt.shared_input_objects())),
806            _ => Either::Right(Either::Right(iter::empty())),
807        }
808    }
809
810    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)> {
811        match &self {
812            Self::Programmable(pt) => pt.move_calls(),
813            _ => vec![],
814        }
815    }
816
817    fn receiving_objects(&self) -> Vec<ObjectRef> {
818        match &self {
819            #[allow(deprecated)]
820            TransactionKind::Genesis(_)
821            | TransactionKind::ConsensusCommitPrologueV1(_)
822            | TransactionKind::AuthenticatorStateUpdateV1Deprecated
823            | TransactionKind::RandomnessStateUpdate(_)
824            | TransactionKind::EndOfEpoch(_) => vec![],
825            TransactionKind::Programmable(pt) => pt.receiving_objects(),
826            _ => unimplemented!(
827                "a new TransactionKind enum variant was added and needs to be handled"
828            ),
829        }
830    }
831
832    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
833        let input_objects = match &self {
834            Self::Genesis(_) => {
835                vec![]
836            }
837            Self::ConsensusCommitPrologueV1(_) => {
838                vec![InputObjectKind::SharedMoveObject {
839                    id: ObjectId::CLOCK,
840                    initial_shared_version: IOTA_CLOCK_OBJECT_SHARED_VERSION,
841                    mutable: true,
842                }]
843            }
844            #[allow(deprecated)]
845            Self::AuthenticatorStateUpdateV1Deprecated => {
846                // Deprecated: Authenticator state (JWK) is deprecated and
847                // was never enabled. These transaction kinds are retained
848                // only for BCS enum variant compatibility.
849                vec![]
850            }
851            Self::RandomnessStateUpdate(update) => {
852                vec![InputObjectKind::SharedMoveObject {
853                    id: ObjectId::RANDOMNESS_STATE,
854                    initial_shared_version: update.randomness_obj_initial_shared_version,
855                    mutable: true,
856                }]
857            }
858            Self::EndOfEpoch(txns) => {
859                // Dedup since transactions may have an overlap in input objects.
860                // Note: it's critical to ensure the order of inputs are deterministic.
861                let before_dedup: Vec<_> =
862                    txns.iter().flat_map(|txn| txn.input_objects()).collect();
863                let mut has_seen = HashSet::new();
864                let mut after_dedup = vec![];
865                for obj in before_dedup {
866                    if has_seen.insert(obj) {
867                        after_dedup.push(obj);
868                    }
869                }
870                after_dedup
871            }
872            Self::Programmable(p) => return p.input_objects(),
873            _ => unimplemented!(
874                "a new TransactionKind enum variant was added and needs to be handled"
875            ),
876        };
877        // Ensure that there are no duplicate inputs. This cannot be removed because:
878        // In [`AuthorityState::check_locks`], we check that there are no duplicate
879        // mutable input objects, which would have made this check here
880        // unnecessary. However, we do plan to allow shared objects show up more
881        // than once in multiple single transactions down the line. Once we have
882        // that, we need check here to make sure the same shared object doesn't
883        // show up more than once in the same single transaction.
884        let mut used = HashSet::new();
885        if !input_objects.iter().all(|o| used.insert(o.object_id())) {
886            return Err(UserInputError::DuplicateObjectRefInput);
887        }
888        Ok(input_objects)
889    }
890
891    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
892        match self {
893            TransactionKind::Programmable(p) => p.validity_check(config)?,
894            // All transaction kinds below are assumed to be system,
895            // and no validity or limit checks are performed.
896            TransactionKind::Genesis(_) | TransactionKind::ConsensusCommitPrologueV1(_) => (),
897            TransactionKind::EndOfEpoch(txns) => {
898                for tx in txns {
899                    tx.validity_check(config)?;
900                }
901            }
902
903            #[allow(deprecated)]
904            TransactionKind::AuthenticatorStateUpdateV1Deprecated => {
905                // Deprecated: Authenticator state (JWK) is deprecated and
906                // was never enabled. These transaction kinds are retained
907                // only for BCS enum variant compatibility.
908                return Err(UserInputError::Unsupported(
909                    "authenticator state transactions are deprecated and were never created on IOTA"
910                        .to_string(),
911                ));
912            }
913            TransactionKind::RandomnessStateUpdate(_) => (),
914            _ => unimplemented!(
915                "a new TransactionKind enum variant was added and needs to be handled"
916            ),
917        };
918        Ok(())
919    }
920
921    fn iter_commands(&self) -> impl Iterator<Item = &Command> {
922        match self {
923            TransactionKind::Programmable(pt) => pt.commands.iter(),
924            _ => [].iter(),
925        }
926    }
927
928    fn name(&self) -> &'static str {
929        match self {
930            Self::Genesis(_) => "Genesis",
931            Self::ConsensusCommitPrologueV1(_) => "ConsensusCommitPrologueV1",
932            Self::Programmable(_) => "Programmable",
933            #[allow(deprecated)]
934            Self::AuthenticatorStateUpdateV1Deprecated => "AuthenticatorStateUpdateV1Deprecated",
935            Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
936            Self::EndOfEpoch(_) => "EndOfEpoch",
937            _ => unimplemented!(
938                "a new TransactionKind enum variant was added and needs to be handled"
939            ),
940        }
941    }
942}
943
944/// API for accessing and constructing [`TransactionData`].
945///
946/// This trait provides node-internal methods for:
947/// - **Accessors**: reading transaction fields (sender, kind, gas, expiration,
948///   etc.)
949/// - **Queries**: inspecting transaction properties (shared objects, Move
950///   calls, sponsorship)
951/// - **Validation**: checking transaction validity against protocol config
952/// - **Constructors**: building new transactions (transfers, Move calls,
953///   programmable txs, etc.)
954///
955/// Note: The `iota-rust-sdk` crate (`iota-sdk-types`) defines its own
956/// [`Transaction`] type with additional client-facing methods.
957pub trait TransactionDataAPI {
958    /// Returns the address of the transaction sender.
959    fn sender(&self) -> IotaAddress;
960
961    /// Returns a reference to the transaction kind.
962    fn kind(&self) -> &TransactionKind;
963
964    /// Returns a mutable reference to the transaction kind.
965    fn kind_mut(&mut self) -> &mut TransactionKind;
966
967    /// Consumes self and returns the transaction kind.
968    fn into_kind(self) -> TransactionKind;
969
970    /// Returns the transaction signer(s). Includes both the sender and the gas
971    /// owner if they differ (i.e. for sponsored transactions).
972    fn signers(&self) -> NonEmpty<IotaAddress>;
973
974    /// Returns a reference to the gas data (owner, payment objects, price,
975    /// budget).
976    fn gas_data(&self) -> &GasData;
977
978    /// Returns the address that owns the gas payment objects.
979    fn gas_owner(&self) -> IotaAddress;
980
981    /// Returns the gas payment object references.
982    fn gas(&self) -> &[ObjectRef];
983
984    /// Returns the gas price for this transaction.
985    fn gas_price(&self) -> u64;
986
987    /// Returns the gas budget for this transaction.
988    fn gas_budget(&self) -> u64;
989
990    /// Returns the transaction expiration.
991    fn expiration(&self) -> &TransactionExpiration;
992
993    /// Returns a list of the transaction data shared input objects.
994    ///
995    /// IMPORTANT: This function does not return shared objects associated with
996    /// `MoveAuthenticator` signatures. To check those objects as well, use the
997    /// corresponding function from `SenderSignedData`.
998    fn shared_input_objects(&self) -> Vec<SharedObjectRef>;
999
1000    /// Returns a list of Move calls as `(package_id, module_name,
1001    /// function_name)` tuples.
1002    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)>;
1003
1004    /// Returns all input objects required by this transaction.
1005    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
1006
1007    /// Returns object references for all objects being received in this
1008    /// transaction.
1009    fn receiving_objects(&self) -> Vec<ObjectRef>;
1010
1011    /// Validates the transaction data against the given protocol config,
1012    /// including gas checks.
1013    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
1014
1015    /// Validates the transaction data against the given protocol config,
1016    /// skipping gas-related checks.
1017    fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult;
1018
1019    /// Check if the transaction is compliant with sponsorship.
1020    fn check_sponsorship(&self) -> UserInputResult;
1021
1022    /// Returns `true` if this is a system transaction.
1023    fn is_system_tx(&self) -> bool;
1024    /// Returns `true` if this is the genesis transaction.
1025    fn is_genesis_tx(&self) -> bool;
1026
1027    /// returns true if the transaction is one that is specially sequenced to
1028    /// run at the very end of the epoch
1029    fn is_end_of_epoch_tx(&self) -> bool;
1030
1031    /// Check if the transaction is sponsored (namely gas owner != sender)
1032    fn is_sponsored_tx(&self) -> bool;
1033
1034    /// Returns a mutable reference to the sender address. **Testing only.**
1035    fn sender_mut_for_testing(&mut self) -> &mut IotaAddress;
1036
1037    /// Returns a mutable reference to the gas data.
1038    fn gas_data_mut(&mut self) -> &mut GasData;
1039
1040    /// Returns a mutable reference to the expiration. **Testing only.**
1041    fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration;
1042
1043    /// Creates a new system transaction with no gas payment. Used for
1044    /// validator-initiated transactions (epoch changes, checkpoints, etc.).
1045    fn new_system_transaction(kind: TransactionKind) -> TransactionData;
1046
1047    /// Creates a new transaction with a single gas payment coin. The sender
1048    /// is also the gas owner.
1049    #[allow(clippy::new_ret_no_self)]
1050    fn new(
1051        kind: TransactionKind,
1052        sender: IotaAddress,
1053        gas_payment: ObjectRef,
1054        gas_budget: u64,
1055        gas_price: u64,
1056    ) -> TransactionData;
1057
1058    /// Creates a new transaction with multiple gas payment coins. The sender
1059    /// is also the gas owner.
1060    fn new_with_gas_coins(
1061        kind: TransactionKind,
1062        sender: IotaAddress,
1063        gas_payment: Vec<ObjectRef>,
1064        gas_budget: u64,
1065        gas_price: u64,
1066    ) -> TransactionData;
1067
1068    /// Creates a new transaction with multiple gas payment coins and a
1069    /// separate gas sponsor. Use this for sponsored transactions where
1070    /// the gas owner differs from the sender.
1071    fn new_with_gas_coins_allow_sponsor(
1072        kind: TransactionKind,
1073        sender: IotaAddress,
1074        gas_payment: Vec<ObjectRef>,
1075        gas_budget: u64,
1076        gas_price: u64,
1077        gas_sponsor: IotaAddress,
1078    ) -> TransactionData;
1079
1080    /// Creates a new transaction from a pre-built [`GasData`] struct.
1081    fn new_with_gas_data(
1082        kind: TransactionKind,
1083        sender: IotaAddress,
1084        gas_data: GasData,
1085    ) -> TransactionData;
1086
1087    /// Creates a transaction that calls a single Move function with a single
1088    /// gas payment coin.
1089    fn new_move_call(
1090        sender: IotaAddress,
1091        package: ObjectId,
1092        module: Identifier,
1093        function: Identifier,
1094        type_arguments: Vec<TypeTag>,
1095        gas_payment: ObjectRef,
1096        arguments: Vec<CallArg>,
1097        gas_budget: u64,
1098        gas_price: u64,
1099    ) -> anyhow::Result<TransactionData>;
1100
1101    /// Creates a transaction that calls a single Move function with multiple
1102    /// gas payment coins.
1103    fn new_move_call_with_gas_coins(
1104        sender: IotaAddress,
1105        package: ObjectId,
1106        module: Identifier,
1107        function: Identifier,
1108        type_arguments: Vec<TypeTag>,
1109        gas_payment: Vec<ObjectRef>,
1110        arguments: Vec<CallArg>,
1111        gas_budget: u64,
1112        gas_price: u64,
1113    ) -> anyhow::Result<TransactionData>;
1114
1115    /// Creates a transaction that transfers an object to a recipient.
1116    fn new_transfer(
1117        recipient: IotaAddress,
1118        object_ref: ObjectRef,
1119        sender: IotaAddress,
1120        gas_payment: ObjectRef,
1121        gas_budget: u64,
1122        gas_price: u64,
1123    ) -> TransactionData;
1124
1125    /// Creates a transaction that transfers IOTA coins to a recipient.
1126    /// If `amount` is `None`, the entire gas coin balance (minus gas fees)
1127    /// is transferred.
1128    fn new_transfer_iota(
1129        recipient: IotaAddress,
1130        sender: IotaAddress,
1131        amount: Option<u64>,
1132        gas_payment: ObjectRef,
1133        gas_budget: u64,
1134        gas_price: u64,
1135    ) -> TransactionData;
1136
1137    /// Creates a sponsored transaction that transfers IOTA coins to a
1138    /// recipient. If `amount` is `None`, the entire gas coin balance
1139    /// (minus gas fees) is transferred.
1140    fn new_transfer_iota_allow_sponsor(
1141        recipient: IotaAddress,
1142        sender: IotaAddress,
1143        amount: Option<u64>,
1144        gas_payment: ObjectRef,
1145        gas_budget: u64,
1146        gas_price: u64,
1147        gas_sponsor: IotaAddress,
1148    ) -> TransactionData;
1149
1150    /// Creates a transaction that pays multiple recipients from a set of
1151    /// input coins. The coins are merged and then split to satisfy the
1152    /// specified amounts.
1153    fn new_pay(
1154        sender: IotaAddress,
1155        coins: Vec<ObjectRef>,
1156        recipients: Vec<IotaAddress>,
1157        amounts: Vec<u64>,
1158        gas_payment: ObjectRef,
1159        gas_budget: u64,
1160        gas_price: u64,
1161    ) -> anyhow::Result<TransactionData>;
1162
1163    /// Creates a transaction that pays multiple recipients using IOTA coins.
1164    /// Similar to [`Self::new_pay`] but the gas coin is also used as an
1165    /// input coin.
1166    fn new_pay_iota(
1167        sender: IotaAddress,
1168        coins: Vec<ObjectRef>,
1169        recipients: Vec<IotaAddress>,
1170        amounts: Vec<u64>,
1171        gas_payment: ObjectRef,
1172        gas_budget: u64,
1173        gas_price: u64,
1174    ) -> anyhow::Result<TransactionData>;
1175
1176    /// Creates a transaction that sends all IOTA from the given coins to a
1177    /// single recipient. The gas coin is included as an input coin.
1178    fn new_pay_all_iota(
1179        sender: IotaAddress,
1180        coins: Vec<ObjectRef>,
1181        recipient: IotaAddress,
1182        gas_payment: ObjectRef,
1183        gas_budget: u64,
1184        gas_price: u64,
1185    ) -> TransactionData;
1186
1187    /// Creates a transaction that splits a coin into multiple coins with the
1188    /// specified amounts.
1189    fn new_split_coin(
1190        sender: IotaAddress,
1191        coin: ObjectRef,
1192        amounts: Vec<u64>,
1193        gas_payment: ObjectRef,
1194        gas_budget: u64,
1195        gas_price: u64,
1196    ) -> TransactionData;
1197
1198    /// Creates a transaction that publishes new Move modules.
1199    fn new_module(
1200        sender: IotaAddress,
1201        gas_payment: ObjectRef,
1202        modules: Vec<Vec<u8>>,
1203        dep_ids: Vec<ObjectId>,
1204        gas_budget: u64,
1205        gas_price: u64,
1206    ) -> TransactionData;
1207
1208    /// Creates a transaction that upgrades an existing Move package.
1209    /// Requires the upgrade capability object and the upgrade policy.
1210    fn new_upgrade(
1211        sender: IotaAddress,
1212        gas_payment: ObjectRef,
1213        package_id: ObjectId,
1214        modules: Vec<Vec<u8>>,
1215        dep_ids: Vec<ObjectId>,
1216        upgrade_capability_and_owner: (ObjectRef, Owner),
1217        upgrade_policy: u8,
1218        digest: Vec<u8>,
1219        gas_budget: u64,
1220        gas_price: u64,
1221    ) -> anyhow::Result<TransactionData>;
1222
1223    /// Creates a programmable transaction with multiple gas payment coins.
1224    /// The sender is also the gas owner.
1225    fn new_programmable(
1226        sender: IotaAddress,
1227        gas_payment: Vec<ObjectRef>,
1228        pt: ProgrammableTransaction,
1229        gas_budget: u64,
1230        gas_price: u64,
1231    ) -> TransactionData;
1232
1233    /// Creates a programmable transaction with multiple gas payment coins
1234    /// and a separate gas sponsor.
1235    fn new_programmable_allow_sponsor(
1236        sender: IotaAddress,
1237        gas_payment: Vec<ObjectRef>,
1238        pt: ProgrammableTransaction,
1239        gas_budget: u64,
1240        gas_price: u64,
1241        sponsor: IotaAddress,
1242    ) -> TransactionData;
1243
1244    /// Returns the internal message version number.
1245    fn message_version(&self) -> u64;
1246
1247    /// Consumes self and returns the transaction kind, sender address, and
1248    /// gas payment object references as a tuple.
1249    fn execution_parts(&self) -> (TransactionKind, IotaAddress, GasData);
1250}
1251
1252impl TransactionDataAPI for TransactionData {
1253    fn sender(&self) -> IotaAddress {
1254        match self {
1255            Self::V1(v1) => v1.sender,
1256            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1257        }
1258    }
1259
1260    fn kind(&self) -> &TransactionKind {
1261        match self {
1262            Self::V1(v1) => &v1.kind,
1263            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1264        }
1265    }
1266
1267    fn kind_mut(&mut self) -> &mut TransactionKind {
1268        match self {
1269            Self::V1(v1) => &mut v1.kind,
1270            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1271        }
1272    }
1273
1274    fn into_kind(self) -> TransactionKind {
1275        match self {
1276            Self::V1(v1) => v1.kind,
1277            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1278        }
1279    }
1280
1281    fn signers(&self) -> NonEmpty<IotaAddress> {
1282        let mut signers = nonempty![self.sender()];
1283        if self.gas_owner() != self.sender() {
1284            signers.push(self.gas_owner());
1285        }
1286        signers
1287    }
1288
1289    fn gas_data(&self) -> &GasData {
1290        match self {
1291            Self::V1(v1) => &v1.gas_payment,
1292            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1293        }
1294    }
1295
1296    fn gas_owner(&self) -> IotaAddress {
1297        self.gas_data().owner
1298    }
1299
1300    fn gas(&self) -> &[ObjectRef] {
1301        &self.gas_data().objects
1302    }
1303
1304    fn gas_price(&self) -> u64 {
1305        self.gas_data().price
1306    }
1307
1308    fn gas_budget(&self) -> u64 {
1309        self.gas_data().budget
1310    }
1311
1312    fn expiration(&self) -> &TransactionExpiration {
1313        match self {
1314            Self::V1(v1) => &v1.expiration,
1315            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1316        }
1317    }
1318
1319    fn shared_input_objects(&self) -> Vec<SharedObjectRef> {
1320        self.kind().shared_input_objects().collect()
1321    }
1322
1323    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)> {
1324        self.kind().move_calls()
1325    }
1326
1327    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
1328        let mut inputs = self.kind().input_objects()?;
1329
1330        if !self.kind().is_system() {
1331            inputs.extend(
1332                self.gas()
1333                    .iter()
1334                    .map(|obj_ref| InputObjectKind::ImmOrOwnedMoveObject(*obj_ref)),
1335            );
1336        }
1337        Ok(inputs)
1338    }
1339
1340    fn receiving_objects(&self) -> Vec<ObjectRef> {
1341        self.kind().receiving_objects()
1342    }
1343
1344    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1345        fp_ensure!(!self.gas().is_empty(), UserInputError::MissingGasPayment);
1346        fp_ensure!(
1347            self.gas().len() < config.max_gas_payment_objects() as usize,
1348            UserInputError::SizeLimitExceeded {
1349                limit: "maximum number of gas payment objects".to_string(),
1350                value: config.max_gas_payment_objects().to_string()
1351            }
1352        );
1353        self.validity_check_no_gas_check(config)
1354    }
1355
1356    #[instrument(level = "trace", skip_all)]
1357    fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult {
1358        self.kind().validity_check(config)?;
1359        self.check_sponsorship()
1360    }
1361
1362    fn is_sponsored_tx(&self) -> bool {
1363        self.gas_owner() != self.sender()
1364    }
1365
1366    fn check_sponsorship(&self) -> UserInputResult {
1367        if self.gas_owner() == self.sender() {
1368            return Ok(());
1369        }
1370        if matches!(self.kind(), TransactionKind::Programmable(_)) {
1371            return Ok(());
1372        }
1373        Err(UserInputError::UnsupportedSponsoredTransactionKind)
1374    }
1375
1376    fn is_end_of_epoch_tx(&self) -> bool {
1377        matches!(self.kind(), TransactionKind::EndOfEpoch(_))
1378    }
1379
1380    fn is_system_tx(&self) -> bool {
1381        self.kind().is_system()
1382    }
1383
1384    fn is_genesis_tx(&self) -> bool {
1385        matches!(self.kind(), TransactionKind::Genesis(_))
1386    }
1387
1388    fn sender_mut_for_testing(&mut self) -> &mut IotaAddress {
1389        match self {
1390            Self::V1(v1) => &mut v1.sender,
1391            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1392        }
1393    }
1394
1395    fn gas_data_mut(&mut self) -> &mut GasData {
1396        match self {
1397            Self::V1(v1) => &mut v1.gas_payment,
1398            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1399        }
1400    }
1401
1402    fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration {
1403        match self {
1404            Self::V1(v1) => &mut v1.expiration,
1405            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1406        }
1407    }
1408
1409    fn new_system_transaction(kind: TransactionKind) -> TransactionData {
1410        assert!(kind.is_system());
1411        let sender = IotaAddress::ZERO;
1412        TransactionData::V1(TransactionDataV1 {
1413            kind,
1414            sender,
1415            gas_payment: GasData {
1416                price: GAS_PRICE_FOR_SYSTEM_TX,
1417                owner: sender,
1418                objects: vec![ObjectRef::new(
1419                    ObjectId::ZERO,
1420                    SequenceNumber::default(),
1421                    ObjectDigest::MIN,
1422                )],
1423                budget: 0,
1424            },
1425            expiration: TransactionExpiration::None,
1426        })
1427    }
1428
1429    fn new(
1430        kind: TransactionKind,
1431        sender: IotaAddress,
1432        gas_payment: ObjectRef,
1433        gas_budget: u64,
1434        gas_price: u64,
1435    ) -> TransactionData {
1436        TransactionData::V1(TransactionDataV1 {
1437            kind,
1438            sender,
1439            gas_payment: GasData {
1440                price: gas_price,
1441                owner: sender,
1442                objects: vec![gas_payment],
1443                budget: gas_budget,
1444            },
1445            expiration: TransactionExpiration::None,
1446        })
1447    }
1448
1449    fn new_with_gas_coins(
1450        kind: TransactionKind,
1451        sender: IotaAddress,
1452        gas_payment: Vec<ObjectRef>,
1453        gas_budget: u64,
1454        gas_price: u64,
1455    ) -> TransactionData {
1456        TransactionData::new_with_gas_coins_allow_sponsor(
1457            kind,
1458            sender,
1459            gas_payment,
1460            gas_budget,
1461            gas_price,
1462            sender,
1463        )
1464    }
1465
1466    fn new_with_gas_coins_allow_sponsor(
1467        kind: TransactionKind,
1468        sender: IotaAddress,
1469        gas_payment: Vec<ObjectRef>,
1470        gas_budget: u64,
1471        gas_price: u64,
1472        gas_sponsor: IotaAddress,
1473    ) -> TransactionData {
1474        TransactionData::V1(TransactionDataV1 {
1475            kind,
1476            sender,
1477            gas_payment: GasData {
1478                price: gas_price,
1479                owner: gas_sponsor,
1480                objects: gas_payment,
1481                budget: gas_budget,
1482            },
1483            expiration: TransactionExpiration::None,
1484        })
1485    }
1486
1487    fn new_with_gas_data(
1488        kind: TransactionKind,
1489        sender: IotaAddress,
1490        gas_data: GasData,
1491    ) -> TransactionData {
1492        TransactionData::V1(TransactionDataV1 {
1493            kind,
1494            sender,
1495            gas_payment: gas_data,
1496            expiration: TransactionExpiration::None,
1497        })
1498    }
1499
1500    fn new_move_call(
1501        sender: IotaAddress,
1502        package: ObjectId,
1503        module: Identifier,
1504        function: Identifier,
1505        type_arguments: Vec<TypeTag>,
1506        gas_payment: ObjectRef,
1507        arguments: Vec<CallArg>,
1508        gas_budget: u64,
1509        gas_price: u64,
1510    ) -> anyhow::Result<TransactionData> {
1511        TransactionData::new_move_call_with_gas_coins(
1512            sender,
1513            package,
1514            module,
1515            function,
1516            type_arguments,
1517            vec![gas_payment],
1518            arguments,
1519            gas_budget,
1520            gas_price,
1521        )
1522    }
1523
1524    fn new_move_call_with_gas_coins(
1525        sender: IotaAddress,
1526        package: ObjectId,
1527        module: Identifier,
1528        function: Identifier,
1529        type_arguments: Vec<TypeTag>,
1530        gas_payment: Vec<ObjectRef>,
1531        arguments: Vec<CallArg>,
1532        gas_budget: u64,
1533        gas_price: u64,
1534    ) -> anyhow::Result<TransactionData> {
1535        let pt = {
1536            let mut builder = ProgrammableTransactionBuilder::new();
1537            builder.move_call(package, module, function, type_arguments, arguments)?;
1538            builder.finish()
1539        };
1540        Ok(TransactionData::new_programmable(
1541            sender,
1542            gas_payment,
1543            pt,
1544            gas_budget,
1545            gas_price,
1546        ))
1547    }
1548
1549    fn new_transfer(
1550        recipient: IotaAddress,
1551        object_ref: ObjectRef,
1552        sender: IotaAddress,
1553        gas_payment: ObjectRef,
1554        gas_budget: u64,
1555        gas_price: u64,
1556    ) -> TransactionData {
1557        let pt = {
1558            let mut builder = ProgrammableTransactionBuilder::new();
1559            builder.transfer_object(recipient, object_ref).unwrap();
1560            builder.finish()
1561        };
1562        TransactionData::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
1563    }
1564
1565    fn new_transfer_iota(
1566        recipient: IotaAddress,
1567        sender: IotaAddress,
1568        amount: Option<u64>,
1569        gas_payment: ObjectRef,
1570        gas_budget: u64,
1571        gas_price: u64,
1572    ) -> TransactionData {
1573        TransactionData::new_transfer_iota_allow_sponsor(
1574            recipient,
1575            sender,
1576            amount,
1577            gas_payment,
1578            gas_budget,
1579            gas_price,
1580            sender,
1581        )
1582    }
1583
1584    fn new_transfer_iota_allow_sponsor(
1585        recipient: IotaAddress,
1586        sender: IotaAddress,
1587        amount: Option<u64>,
1588        gas_payment: ObjectRef,
1589        gas_budget: u64,
1590        gas_price: u64,
1591        gas_sponsor: IotaAddress,
1592    ) -> TransactionData {
1593        let pt = {
1594            let mut builder = ProgrammableTransactionBuilder::new();
1595            builder.transfer_iota(recipient, amount);
1596            builder.finish()
1597        };
1598        TransactionData::new_programmable_allow_sponsor(
1599            sender,
1600            vec![gas_payment],
1601            pt,
1602            gas_budget,
1603            gas_price,
1604            gas_sponsor,
1605        )
1606    }
1607
1608    fn new_pay(
1609        sender: IotaAddress,
1610        coins: Vec<ObjectRef>,
1611        recipients: Vec<IotaAddress>,
1612        amounts: Vec<u64>,
1613        gas_payment: ObjectRef,
1614        gas_budget: u64,
1615        gas_price: u64,
1616    ) -> anyhow::Result<TransactionData> {
1617        let pt = {
1618            let mut builder = ProgrammableTransactionBuilder::new();
1619            builder.pay(coins, recipients, amounts)?;
1620            builder.finish()
1621        };
1622        Ok(TransactionData::new_programmable(
1623            sender,
1624            vec![gas_payment],
1625            pt,
1626            gas_budget,
1627            gas_price,
1628        ))
1629    }
1630
1631    fn new_pay_iota(
1632        sender: IotaAddress,
1633        mut coins: Vec<ObjectRef>,
1634        recipients: Vec<IotaAddress>,
1635        amounts: Vec<u64>,
1636        gas_payment: ObjectRef,
1637        gas_budget: u64,
1638        gas_price: u64,
1639    ) -> anyhow::Result<TransactionData> {
1640        coins.insert(0, gas_payment);
1641        let pt = {
1642            let mut builder = ProgrammableTransactionBuilder::new();
1643            builder.pay_iota(recipients, amounts)?;
1644            builder.finish()
1645        };
1646        Ok(TransactionData::new_programmable(
1647            sender, coins, pt, gas_budget, gas_price,
1648        ))
1649    }
1650
1651    fn new_pay_all_iota(
1652        sender: IotaAddress,
1653        mut coins: Vec<ObjectRef>,
1654        recipient: IotaAddress,
1655        gas_payment: ObjectRef,
1656        gas_budget: u64,
1657        gas_price: u64,
1658    ) -> TransactionData {
1659        coins.insert(0, gas_payment);
1660        let pt = {
1661            let mut builder = ProgrammableTransactionBuilder::new();
1662            builder.pay_all_iota(recipient);
1663            builder.finish()
1664        };
1665        TransactionData::new_programmable(sender, coins, pt, gas_budget, gas_price)
1666    }
1667
1668    fn new_split_coin(
1669        sender: IotaAddress,
1670        coin: ObjectRef,
1671        amounts: Vec<u64>,
1672        gas_payment: ObjectRef,
1673        gas_budget: u64,
1674        gas_price: u64,
1675    ) -> TransactionData {
1676        let pt = {
1677            let mut builder = ProgrammableTransactionBuilder::new();
1678            builder.split_coin(sender, coin, amounts);
1679            builder.finish()
1680        };
1681        TransactionData::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
1682    }
1683
1684    fn new_module(
1685        sender: IotaAddress,
1686        gas_payment: ObjectRef,
1687        modules: Vec<Vec<u8>>,
1688        dep_ids: Vec<ObjectId>,
1689        gas_budget: u64,
1690        gas_price: u64,
1691    ) -> TransactionData {
1692        let pt = {
1693            let mut builder = ProgrammableTransactionBuilder::new();
1694            let upgrade_cap = builder.publish_upgradeable(modules, dep_ids);
1695            builder.transfer_arg(sender, upgrade_cap);
1696            builder.finish()
1697        };
1698        TransactionData::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
1699    }
1700
1701    fn new_upgrade(
1702        sender: IotaAddress,
1703        gas_payment: ObjectRef,
1704        package_id: ObjectId,
1705        modules: Vec<Vec<u8>>,
1706        dep_ids: Vec<ObjectId>,
1707        (upgrade_capability, capability_owner): (ObjectRef, Owner),
1708        upgrade_policy: u8,
1709        digest: Vec<u8>,
1710        gas_budget: u64,
1711        gas_price: u64,
1712    ) -> anyhow::Result<TransactionData> {
1713        let pt = {
1714            let mut builder = ProgrammableTransactionBuilder::new();
1715            let capability_arg = match capability_owner {
1716                Owner::Address(_) => CallArg::ImmutableOrOwned(upgrade_capability),
1717                Owner::Shared(initial_shared_version) => CallArg::Shared(SharedObjectRef::new(
1718                    upgrade_capability.object_id,
1719                    initial_shared_version,
1720                    true,
1721                )),
1722                Owner::Immutable => {
1723                    bail!("Upgrade capability is stored immutably and cannot be used for upgrades");
1724                }
1725                Owner::Object(_) => {
1726                    bail!("Upgrade capability controlled by object");
1727                }
1728                _ => unimplemented!("a new Owner enum variant was added and needs to be handled"),
1729            };
1730            builder.obj(capability_arg).unwrap();
1731            let upgrade_arg = builder.pure(upgrade_policy).unwrap();
1732            let digest_arg = builder.pure(digest).unwrap();
1733            let upgrade_ticket = builder.programmable_move_call(
1734                ObjectId::FRAMEWORK,
1735                Identifier::PACKAGE_MODULE,
1736                Identifier::from_static("authorize_upgrade"),
1737                vec![],
1738                vec![Argument::Input(0), upgrade_arg, digest_arg],
1739            );
1740            let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
1741
1742            builder.programmable_move_call(
1743                ObjectId::FRAMEWORK,
1744                Identifier::PACKAGE_MODULE,
1745                Identifier::from_static("commit_upgrade"),
1746                vec![],
1747                vec![Argument::Input(0), upgrade_receipt],
1748            );
1749
1750            builder.finish()
1751        };
1752        Ok(TransactionData::new_programmable(
1753            sender,
1754            vec![gas_payment],
1755            pt,
1756            gas_budget,
1757            gas_price,
1758        ))
1759    }
1760
1761    fn new_programmable(
1762        sender: IotaAddress,
1763        gas_payment: Vec<ObjectRef>,
1764        pt: ProgrammableTransaction,
1765        gas_budget: u64,
1766        gas_price: u64,
1767    ) -> TransactionData {
1768        TransactionData::new_programmable_allow_sponsor(
1769            sender,
1770            gas_payment,
1771            pt,
1772            gas_budget,
1773            gas_price,
1774            sender,
1775        )
1776    }
1777
1778    fn new_programmable_allow_sponsor(
1779        sender: IotaAddress,
1780        gas_payment: Vec<ObjectRef>,
1781        pt: ProgrammableTransaction,
1782        gas_budget: u64,
1783        gas_price: u64,
1784        sponsor: IotaAddress,
1785    ) -> TransactionData {
1786        let kind = TransactionKind::Programmable(pt);
1787        TransactionData::new_with_gas_coins_allow_sponsor(
1788            kind,
1789            sender,
1790            gas_payment,
1791            gas_budget,
1792            gas_price,
1793            sponsor,
1794        )
1795    }
1796
1797    fn message_version(&self) -> u64 {
1798        match self {
1799            TransactionData::V1(_) => 1,
1800            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1801        }
1802    }
1803
1804    fn execution_parts(&self) -> (TransactionKind, IotaAddress, GasData) {
1805        (self.kind().clone(), self.sender(), self.gas_data().clone())
1806    }
1807}
1808
1809#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
1810pub struct SenderSignedData(SizeOneVec<SenderSignedTransaction>);
1811
1812#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1813pub struct SenderSignedTransaction {
1814    pub intent_message: IntentMessage<TransactionData>,
1815    /// A list of signatures signed by all transaction participants.
1816    /// 1. non participant signature must not be present.
1817    /// 2. signature order does not matter.
1818    pub tx_signatures: Vec<GenericSignature>,
1819}
1820
1821impl Serialize for SenderSignedTransaction {
1822    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1823    where
1824        S: serde::Serializer,
1825    {
1826        #[derive(Serialize)]
1827        #[serde(rename = "SenderSignedTransaction")]
1828        struct SignedTxn<'a> {
1829            intent_message: &'a IntentMessage<TransactionData>,
1830            tx_signatures: &'a Vec<GenericSignature>,
1831        }
1832
1833        if self.intent_message().intent != Intent::iota_transaction() {
1834            return Err(serde::ser::Error::custom("invalid Intent for Transaction"));
1835        }
1836
1837        let txn = SignedTxn {
1838            intent_message: self.intent_message(),
1839            tx_signatures: &self.tx_signatures,
1840        };
1841        txn.serialize(serializer)
1842    }
1843}
1844
1845impl<'de> Deserialize<'de> for SenderSignedTransaction {
1846    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1847    where
1848        D: serde::Deserializer<'de>,
1849    {
1850        #[derive(Deserialize)]
1851        #[serde(rename = "SenderSignedTransaction")]
1852        struct SignedTxn {
1853            intent_message: IntentMessage<TransactionData>,
1854            tx_signatures: Vec<GenericSignature>,
1855        }
1856
1857        let SignedTxn {
1858            intent_message,
1859            tx_signatures,
1860        } = Deserialize::deserialize(deserializer)?;
1861
1862        if intent_message.intent != Intent::iota_transaction() {
1863            return Err(serde::de::Error::custom("invalid Intent for Transaction"));
1864        }
1865
1866        Ok(Self {
1867            intent_message,
1868            tx_signatures,
1869        })
1870    }
1871}
1872
1873impl SenderSignedTransaction {
1874    pub(crate) fn get_signer_sig_mapping(
1875        &self,
1876    ) -> IotaResult<BTreeMap<IotaAddress, &GenericSignature>> {
1877        let mut mapping = BTreeMap::new();
1878        for sig in &self.tx_signatures {
1879            let address = sig.try_into()?;
1880            mapping.insert(address, sig);
1881        }
1882        Ok(mapping)
1883    }
1884
1885    pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
1886        &self.intent_message
1887    }
1888}
1889
1890impl SenderSignedData {
1891    pub fn new(tx_data: TransactionData, tx_signatures: Vec<GenericSignature>) -> Self {
1892        Self(SizeOneVec::new(SenderSignedTransaction {
1893            intent_message: IntentMessage::new(Intent::iota_transaction(), tx_data),
1894            tx_signatures,
1895        }))
1896    }
1897
1898    pub fn new_from_sender_signature(tx_data: TransactionData, tx_signature: Signature) -> Self {
1899        Self(SizeOneVec::new(SenderSignedTransaction {
1900            intent_message: IntentMessage::new(Intent::iota_transaction(), tx_data),
1901            tx_signatures: vec![tx_signature.into()],
1902        }))
1903    }
1904
1905    pub fn inner(&self) -> &SenderSignedTransaction {
1906        self.0.element()
1907    }
1908
1909    pub fn into_inner(self) -> SenderSignedTransaction {
1910        self.0.into_inner()
1911    }
1912
1913    pub fn inner_mut(&mut self) -> &mut SenderSignedTransaction {
1914        self.0.element_mut()
1915    }
1916
1917    // This function does not check validity of the signature
1918    // or perform any de-dup checks.
1919    pub fn add_signature(&mut self, new_signature: Signature) {
1920        self.inner_mut().tx_signatures.push(new_signature.into());
1921    }
1922
1923    pub(crate) fn get_signer_sig_mapping(
1924        &self,
1925    ) -> IotaResult<BTreeMap<IotaAddress, &GenericSignature>> {
1926        self.inner().get_signer_sig_mapping()
1927    }
1928
1929    pub fn transaction_data(&self) -> &TransactionData {
1930        &self.intent_message().value
1931    }
1932
1933    pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
1934        self.inner().intent_message()
1935    }
1936
1937    pub fn tx_signatures(&self) -> &[GenericSignature] {
1938        &self.inner().tx_signatures
1939    }
1940
1941    pub fn has_upgraded_multisig(&self) -> bool {
1942        self.tx_signatures()
1943            .iter()
1944            .any(|sig| sig.is_upgraded_multisig())
1945    }
1946
1947    #[cfg(test)]
1948    pub fn intent_message_mut_for_testing(&mut self) -> &mut IntentMessage<TransactionData> {
1949        &mut self.inner_mut().intent_message
1950    }
1951
1952    // used cross-crate, so cannot be #[cfg(test)]
1953    pub fn tx_signatures_mut_for_testing(&mut self) -> &mut Vec<GenericSignature> {
1954        &mut self.inner_mut().tx_signatures
1955    }
1956
1957    pub fn full_message_digest(&self) -> SenderSignedDataDigest {
1958        let mut digest = DefaultHash::default();
1959        bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
1960        let hash = digest.finalize();
1961        SenderSignedDataDigest::new(hash.into())
1962    }
1963
1964    pub fn serialized_size(&self) -> IotaResult<usize> {
1965        bcs::serialized_size(self).map_err(|e| IotaError::TransactionSerialization {
1966            error: e.to_string(),
1967        })
1968    }
1969
1970    fn check_user_signature_protocol_compatibility(&self, config: &ProtocolConfig) -> IotaResult {
1971        for sig in &self.inner().tx_signatures {
1972            match sig {
1973                #[allow(deprecated)]
1974                GenericSignature::ZkLoginAuthenticatorDeprecated(_) => {
1975                    return Err(IotaError::UserInput {
1976                        error: UserInputError::Unsupported("zkLogin is not supported".to_string()),
1977                    });
1978                }
1979                GenericSignature::PasskeyAuthenticator(_) => {
1980                    if !config.passkey_auth() {
1981                        return Err(IotaError::UserInput {
1982                            error: UserInputError::Unsupported(
1983                                "passkey is not enabled on this network".to_string(),
1984                            ),
1985                        });
1986                    }
1987                }
1988                GenericSignature::MoveAuthenticator(_) => {
1989                    if !config.enable_move_authentication() {
1990                        return Err(IotaError::UserInput {
1991                            error: UserInputError::Unsupported(
1992                                "`Move authentication` is not enabled on this network".to_string(),
1993                            ),
1994                        });
1995                    }
1996                }
1997                GenericSignature::Signature(_) | GenericSignature::MultiSig(_) => (),
1998            }
1999        }
2000
2001        Ok(())
2002    }
2003
2004    /// Validate untrusted user transaction, including its size, input count,
2005    /// command count, etc.
2006    /// Returns the certificate serialised bytes size.
2007    pub fn validity_check(
2008        &self,
2009        config: &ProtocolConfig,
2010        epoch: EpochId,
2011    ) -> Result<usize, IotaError> {
2012        // Check that the features used by the user signatures are enabled on the
2013        // network.
2014        self.check_user_signature_protocol_compatibility(config)?;
2015
2016        // CRITICAL!!
2017        // Users cannot send system transactions.
2018        let tx_data = self.transaction_data();
2019        fp_ensure!(
2020            !tx_data.is_system_tx(),
2021            IotaError::UserInput {
2022                error: UserInputError::Unsupported(
2023                    "SenderSignedData must not contain system transaction".to_string()
2024                )
2025            }
2026        );
2027
2028        // Checks to see if the transaction has expired
2029        if match &tx_data.expiration() {
2030            TransactionExpiration::None => false,
2031            TransactionExpiration::Epoch(exp_poch) => *exp_poch < epoch,
2032            _ => unimplemented!(
2033                "a new TransactionExpiration enum variant was added and needs to be handled"
2034            ),
2035        } {
2036            return Err(IotaError::TransactionExpired);
2037        }
2038
2039        // Enforce overall transaction size limit.
2040        let tx_size = self.serialized_size()?;
2041        let max_tx_size_bytes = config.max_tx_size_bytes();
2042        fp_ensure!(
2043            tx_size as u64 <= max_tx_size_bytes,
2044            IotaError::UserInput {
2045                error: UserInputError::SizeLimitExceeded {
2046                    limit: format!(
2047                        "serialized transaction size exceeded maximum of {max_tx_size_bytes}"
2048                    ),
2049                    value: tx_size.to_string(),
2050                }
2051            }
2052        );
2053
2054        tx_data
2055            .validity_check(config)
2056            .map_err(Into::<IotaError>::into)?;
2057
2058        self.move_authenticators_validity_check(config)?;
2059
2060        Ok(tx_size)
2061    }
2062
2063    pub fn move_authenticators(&self) -> Vec<&MoveAuthenticator> {
2064        self.tx_signatures()
2065            .iter()
2066            .filter_map(|sig| {
2067                if let GenericSignature::MoveAuthenticator(move_authenticator) = sig {
2068                    Some(move_authenticator)
2069                } else {
2070                    None
2071                }
2072            })
2073            .collect()
2074    }
2075
2076    /// Returns the senders's [`MoveAuthenticator`], if the sender uses one.
2077    pub fn sender_move_authenticator(&self) -> Option<&MoveAuthenticator> {
2078        let sender = self.intent_message().value.sender();
2079
2080        self.move_authenticators()
2081            .into_iter()
2082            .find(|a| match a.address() {
2083                Ok(addr) => addr == sender,
2084                Err(_) => false,
2085            })
2086    }
2087
2088    /// Returns the sponsor's [`MoveAuthenticator`], if the transaction is
2089    /// sponsored and the sponsor uses one.
2090    pub fn sponsor_move_authenticator(&self) -> Option<&MoveAuthenticator> {
2091        let tx_data = self.transaction_data();
2092
2093        if tx_data.is_sponsored_tx() {
2094            let gas_owner = tx_data.gas_owner();
2095
2096            self.move_authenticators()
2097                .into_iter()
2098                .find(|a| match a.address() {
2099                    Ok(addr) => addr == gas_owner,
2100                    Err(_) => false,
2101                })
2102        } else {
2103            None
2104        }
2105    }
2106
2107    /// Computes the auth digest for the sender and, if sponsored, for the
2108    /// sponsor. See [`auth_digest_for_sig`] for the per-signature logic.
2109    pub fn compute_auth_digests(&self) -> IotaResult<(Digest, Option<Digest>)> {
2110        let tx_data = self.transaction_data();
2111
2112        let digest_for_address = |address: IotaAddress| {
2113            self.tx_signatures()
2114                .iter()
2115                .find(|sig| IotaAddress::try_from(*sig).ok() == Some(address))
2116                .ok_or_else(|| IotaError::InvalidSignature {
2117                    error: format!("no signature found for address {address}"),
2118                })
2119                .and_then(auth_digest_for_sig)
2120        };
2121
2122        let sender_auth_digest = digest_for_address(tx_data.sender())?;
2123        let sponsor_auth_digest = if tx_data.is_sponsored_tx() {
2124            Some(digest_for_address(tx_data.gas_owner())?)
2125        } else {
2126            None
2127        };
2128
2129        Ok((sender_auth_digest, sponsor_auth_digest))
2130    }
2131
2132    /// Returns all unique input objects including those from
2133    /// `MoveAuthenticator`s if any for reading.
2134    ///
2135    /// Although some shared objects(with a different mutability flag, for
2136    /// example) can be duplicated in the transaction and authenticators, we
2137    /// load them independently to make it possible to analyze the inputs in
2138    /// the transaction checkers.
2139    pub fn collect_all_input_object_kind_for_reading(&self) -> IotaResult<Vec<InputObjectKind>> {
2140        let mut input_objects_set = self
2141            .transaction_data()
2142            .input_objects()?
2143            .into_iter()
2144            .collect::<HashSet<_>>();
2145
2146        self.move_authenticators()
2147            .into_iter()
2148            .for_each(|authenticator| {
2149                input_objects_set.extend(authenticator.input_objects());
2150            });
2151
2152        Ok(input_objects_set.into_iter().collect::<Vec<_>>())
2153    }
2154
2155    /// Splits the provided input objects into groups:
2156    /// 1. Input objects required by the transaction itself; may contain
2157    ///    duplicates if an IOTA coin is used both as an input and a gas coin.
2158    /// 2. A list of input objects required by each `MoveAuthenticator`(
2159    ///    including the object to authenticate) + the object to authenticate.
2160    pub fn split_input_objects_into_groups_for_reading(
2161        &self,
2162        input_objects: InputObjects,
2163    ) -> IotaResult<(InputObjects, Vec<(InputObjects, ObjectReadResult)>)> {
2164        let input_objects_map = input_objects
2165            .iter()
2166            .map(|o| (&o.input_object_kind, o))
2167            .collect::<HashMap<_, _>>();
2168
2169        let tx_input_objects = self
2170            .transaction_data()
2171            .input_objects()?
2172            .iter()
2173            .map(|k| {
2174                input_objects_map
2175                    .get(k)
2176                    .map(|&r| r.clone())
2177                    .expect("All transaction input objects are expected to be present")
2178            })
2179            .collect::<Vec<_>>()
2180            .into();
2181
2182        let per_authenticator_inputs =
2183            self.move_authenticators()
2184                .into_iter()
2185                .map(|move_authenticator| {
2186                    let authenticator_input_objects = move_authenticator
2187                        .input_objects()
2188                        .iter()
2189                        .map(|k| {
2190                            input_objects_map.get(k).map(|&r| r.clone()).expect(
2191                                "All authenticator input objects are expected to be present",
2192                            )
2193                        })
2194                        .collect::<Vec<_>>()
2195                        .into();
2196
2197                    let account_objects = move_authenticator
2198                        .object_to_authenticate()
2199                        .input_object_kind()
2200                        .iter()
2201                        .map(|k| {
2202                            input_objects_map
2203                                .get(k)
2204                                .map(|&r| r.clone())
2205                                .expect("Account object is expected to be present")
2206                        })
2207                        .collect::<Vec<_>>();
2208
2209                    debug_assert!(
2210                        account_objects.len() == 1,
2211                        "Only one account object must be loaded"
2212                    );
2213
2214                    (
2215                        authenticator_input_objects,
2216                        account_objects
2217                            .into_iter()
2218                            .next()
2219                            .expect("Account object is expected to be present"),
2220                    )
2221                })
2222                .collect();
2223
2224        Ok((tx_input_objects, per_authenticator_inputs))
2225    }
2226
2227    /// Checks if `SenderSignedData` contains at least one shared object.
2228    /// This function checks shared objects from the `MoveAuthenticator`s if
2229    /// any.
2230    pub fn contains_shared_object(&self) -> bool {
2231        !self.shared_input_objects().is_empty()
2232    }
2233
2234    /// Returns an iterator over all shared input objects related to this
2235    /// transaction, including those from `MoveAuthenticator`s if any.
2236    ///
2237    /// If a shared object appears with the same version but different
2238    /// mutability, only one instance which is mutable is returned.
2239    ///
2240    /// Panics if there are shared objects with the same ID but different
2241    /// initial versions.
2242    pub fn shared_input_objects(&self) -> Vec<SharedObjectRef> {
2243        // Vector is used to preserve the order of input objects.
2244        let mut input_objects = self.transaction_data().shared_input_objects();
2245
2246        // Add Move authenticator shared objects if any.
2247        self.move_authenticators()
2248            .into_iter()
2249            .for_each(|move_authenticator| {
2250                for auth_shared_object in move_authenticator.shared_objects() {
2251                    let entry = input_objects
2252                        .iter_mut()
2253                        .find(|o| o.object_id == auth_shared_object.object_id);
2254
2255                    match entry {
2256                        None => input_objects.push(auth_shared_object),
2257                        Some(existing) => {
2258                            left_union_shared_input_objects(existing, &auth_shared_object)
2259                                .expect("union of shared objects should not fail")
2260                        }
2261                    }
2262                }
2263            });
2264
2265        input_objects
2266    }
2267
2268    /// Returns an iterator over all input objects related to this
2269    /// transaction, including those from the `MoveAuthenticator`s if any.
2270    ///
2271    /// If an IOTA coin is used both as an input and as a gas coin, it will
2272    /// appear two times in the returned iterator.
2273    ///
2274    /// If a shared object appears both in the transaction and authenticator
2275    /// with different mutability, only one instance which is mutable is
2276    /// returned.
2277    ///
2278    /// Shared objects with the same ID but different versions are not allowed.
2279    pub fn input_objects(&self) -> IotaResult<Vec<InputObjectKind>> {
2280        // Can contain duplicates in case of using the same IOTA coin as an input and as
2281        // a gas coin.
2282        let mut input_objects = self.transaction_data().input_objects()?;
2283
2284        // Add the `MoveAuthenticator` shared objects if any.
2285        self.move_authenticators().into_iter().try_for_each(
2286            |move_authenticator| -> IotaResult<()> {
2287                for auth_object in move_authenticator.input_objects() {
2288                    let entry = input_objects
2289                        .iter_mut()
2290                        .find(|o| o.object_id() == auth_object.object_id());
2291
2292                    match entry {
2293                        None => input_objects.push(auth_object),
2294                        Some(existing) => existing.left_union_with_checks(&auth_object)?,
2295                    }
2296                }
2297                Ok(())
2298            },
2299        )?;
2300
2301        Ok(input_objects)
2302    }
2303
2304    /// Checks if `SenderSignedData` contains the `Random` object as an
2305    /// input.
2306    /// This function checks shared objects from the `MoveAuthenticator`s if
2307    /// any.
2308    pub fn uses_randomness(&self) -> bool {
2309        self.shared_input_objects()
2310            .iter()
2311            .any(|obj| obj.object_id == ObjectId::RANDOMNESS_STATE)
2312    }
2313
2314    fn move_authenticators_validity_check(&self, config: &ProtocolConfig) -> IotaResult {
2315        let authenticators = self.move_authenticators();
2316
2317        // Check each `MoveAuthenticator` validity.
2318        authenticators
2319            .iter()
2320            .try_for_each(|authenticator| authenticator.validity_check(config))?;
2321
2322        // Additional checks when `MoveAuthenticators` are present.
2323        let authenticators_num = authenticators.len();
2324        if authenticators_num > 0 {
2325            let tx_data = self.transaction_data();
2326
2327            fp_ensure!(
2328                tx_data.kind().is_programmable(),
2329                UserInputError::Unsupported(
2330                    "SenderSignedData with MoveAuthenticator must be a programmable transaction"
2331                        .to_string(),
2332                )
2333                .into()
2334            );
2335
2336            if !config.enable_move_authentication_for_sponsor() {
2337                fp_ensure!(
2338                    authenticators_num == 1,
2339                    UserInputError::Unsupported(
2340                        "SenderSignedData with more than one MoveAuthenticator is not supported"
2341                            .to_string(),
2342                    )
2343                    .into()
2344                );
2345
2346                fp_ensure!(
2347                    self.sender_move_authenticator().is_some(),
2348                    UserInputError::Unsupported(
2349                        "SenderSignedData can have MoveAuthenticator only for the sender"
2350                            .to_string(),
2351                    )
2352                    .into()
2353                );
2354            }
2355
2356            Self::check_move_authenticators_input_consistency(tx_data, &authenticators)?;
2357        }
2358
2359        Ok(())
2360    }
2361
2362    fn check_move_authenticators_input_consistency(
2363        tx_data: &TransactionData,
2364        authenticators: &[&MoveAuthenticator],
2365    ) -> IotaResult {
2366        // Get the input objects from the transaction data kind to skip the gas coins.
2367        let mut checked_inputs = tx_data
2368            .kind()
2369            .input_objects()?
2370            .into_iter()
2371            .map(|o| (o.object_id(), o))
2372            .collect::<HashMap<_, _>>();
2373
2374        authenticators.iter().try_for_each(|authenticator| {
2375            authenticator
2376                .input_objects()
2377                .iter()
2378                .try_for_each(|auth_input_object| {
2379                    match checked_inputs.get(&auth_input_object.object_id()) {
2380                        Some(existing) => {
2381                            auth_input_object.check_consistency_for_authentication(existing)?
2382                        }
2383                        None => {
2384                            checked_inputs
2385                                .insert(auth_input_object.object_id(), *auth_input_object);
2386                        }
2387                    };
2388
2389                    Ok(())
2390                })
2391        })
2392    }
2393}
2394
2395impl Message for SenderSignedData {
2396    type DigestType = TransactionDigest;
2397    const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
2398
2399    /// Computes the tx digest that encodes the Rust type prefix from Signable
2400    /// trait.
2401    fn digest(&self) -> Self::DigestType {
2402        self.intent_message().value.digest()
2403    }
2404}
2405
2406impl<S> Envelope<SenderSignedData, S> {
2407    pub fn sender_address(&self) -> IotaAddress {
2408        self.data().intent_message().value.sender()
2409    }
2410
2411    pub fn gas(&self) -> &[ObjectRef] {
2412        self.data().intent_message().value.gas()
2413    }
2414
2415    // Returns the primary key for this transaction.
2416    pub fn key(&self) -> TransactionKey {
2417        match &self.data().intent_message().value.kind() {
2418            TransactionKind::RandomnessStateUpdate(rsu) => {
2419                TransactionKey::RandomnessRound(rsu.epoch, rsu.randomness_round)
2420            }
2421            _ => TransactionKey::Digest(*self.digest()),
2422        }
2423    }
2424
2425    // Returns non-Digest keys that could be used to refer to this transaction.
2426    //
2427    // At the moment this returns a single Option for efficiency, but if more key
2428    // types are added, the return type could change to Vec<TransactionKey>.
2429    pub fn non_digest_key(&self) -> Option<TransactionKey> {
2430        match &self.data().intent_message().value.kind() {
2431            TransactionKind::RandomnessStateUpdate(rsu) => Some(TransactionKey::RandomnessRound(
2432                rsu.epoch,
2433                rsu.randomness_round,
2434            )),
2435            _ => None,
2436        }
2437    }
2438
2439    pub fn is_system_tx(&self) -> bool {
2440        self.data().intent_message().value.is_system_tx()
2441    }
2442
2443    pub fn is_sponsored_tx(&self) -> bool {
2444        self.data().intent_message().value.is_sponsored_tx()
2445    }
2446}
2447
2448impl Transaction {
2449    pub fn from_data_and_signer(
2450        data: TransactionData,
2451        signers: Vec<&dyn Signer<Signature>>,
2452    ) -> Self {
2453        let signatures = {
2454            let intent_msg = IntentMessage::new(Intent::iota_transaction(), &data);
2455            signers
2456                .into_iter()
2457                .map(|s| Signature::new_secure(&intent_msg, s))
2458                .collect()
2459        };
2460        Self::from_data(data, signatures)
2461    }
2462
2463    // TODO: Rename this function and above to make it clearer.
2464    pub fn from_data(data: TransactionData, signatures: Vec<Signature>) -> Self {
2465        Self::from_generic_sig_data(data, signatures.into_iter().map(|s| s.into()).collect())
2466    }
2467
2468    pub fn signature_from_signer(
2469        data: TransactionData,
2470        intent: Intent,
2471        signer: &dyn Signer<Signature>,
2472    ) -> Signature {
2473        let intent_msg = IntentMessage::new(intent, data);
2474        Signature::new_secure(&intent_msg, signer)
2475    }
2476
2477    pub fn from_generic_sig_data(data: TransactionData, signatures: Vec<GenericSignature>) -> Self {
2478        Self::new(SenderSignedData::new(data, signatures))
2479    }
2480
2481    /// Returns the Base64 encoded tx_bytes
2482    /// and a list of Base64 encoded [enum GenericSignature].
2483    pub fn to_tx_bytes_and_signatures(&self) -> (Base64, Vec<Base64>) {
2484        (
2485            Base64::from_bytes(&bcs::to_bytes(&self.data().intent_message().value).unwrap()),
2486            self.data()
2487                .inner()
2488                .tx_signatures
2489                .iter()
2490                .map(|s| Base64::from_bytes(s.as_ref()))
2491                .collect(),
2492        )
2493    }
2494}
2495
2496impl VerifiedTransaction {
2497    pub fn new_genesis_transaction(objects: Vec<GenesisObject>, events: Vec<Event>) -> Self {
2498        GenesisTransaction { objects, events }
2499            .pipe(TransactionKind::Genesis)
2500            .pipe(Self::new_system_transaction)
2501    }
2502
2503    pub fn new_consensus_commit_prologue_v1(
2504        epoch: u64,
2505        round: u64,
2506        commit_timestamp_ms: CheckpointTimestamp,
2507        consensus_commit_digest: ConsensusCommitDigest,
2508        cancelled_transactions: Vec<CancelledTransaction>,
2509    ) -> Self {
2510        ConsensusCommitPrologueV1 {
2511            epoch,
2512            round,
2513            // sub_dag_index is reserved for when we have multi commits per round.
2514            sub_dag_index: None,
2515            commit_timestamp_ms,
2516            consensus_commit_digest,
2517            consensus_determined_version_assignments:
2518                ConsensusDeterminedVersionAssignments::CancelledTransactions {
2519                    cancelled_transactions,
2520                },
2521        }
2522        .pipe(TransactionKind::ConsensusCommitPrologueV1)
2523        .pipe(Self::new_system_transaction)
2524    }
2525
2526    pub fn new_randomness_state_update(
2527        epoch: u64,
2528        randomness_round: RandomnessRound,
2529        random_bytes: Vec<u8>,
2530        randomness_obj_initial_shared_version: SequenceNumber,
2531    ) -> Self {
2532        RandomnessStateUpdate {
2533            epoch,
2534            randomness_round,
2535            random_bytes,
2536            randomness_obj_initial_shared_version,
2537        }
2538        .pipe(TransactionKind::RandomnessStateUpdate)
2539        .pipe(Self::new_system_transaction)
2540    }
2541
2542    pub fn new_end_of_epoch_transaction(txns: Vec<EndOfEpochTransactionKind>) -> Self {
2543        TransactionKind::EndOfEpoch(txns).pipe(Self::new_system_transaction)
2544    }
2545
2546    fn new_system_transaction(system_transaction: TransactionKind) -> Self {
2547        system_transaction
2548            .pipe(TransactionData::new_system_transaction)
2549            .pipe(|data| {
2550                SenderSignedData::new_from_sender_signature(
2551                    data,
2552                    Ed25519IotaSignature::from_bytes(&[0; Ed25519IotaSignature::LENGTH])
2553                        .unwrap()
2554                        .into(),
2555                )
2556            })
2557            .pipe(Transaction::new)
2558            .pipe(Self::new_from_verified)
2559    }
2560}
2561
2562impl VerifiedSignedTransaction {
2563    /// Use signing key to create a signed object.
2564    #[instrument(level = "trace", skip_all)]
2565    pub fn new(
2566        epoch: EpochId,
2567        transaction: VerifiedTransaction,
2568        authority: AuthorityName,
2569        secret: &dyn Signer<AuthoritySignature>,
2570    ) -> Self {
2571        Self::new_from_verified(SignedTransaction::new(
2572            epoch,
2573            transaction.into_inner().into_data(),
2574            secret,
2575            authority,
2576        ))
2577    }
2578}
2579
2580/// A transaction that is signed by a sender but not yet by an authority.
2581pub type Transaction = Envelope<SenderSignedData, EmptySignInfo>;
2582pub type VerifiedTransaction = VerifiedEnvelope<SenderSignedData, EmptySignInfo>;
2583pub type TrustedTransaction = TrustedEnvelope<SenderSignedData, EmptySignInfo>;
2584
2585/// A transaction that is signed by a sender and also by an authority.
2586pub type SignedTransaction = Envelope<SenderSignedData, AuthoritySignInfo>;
2587pub type VerifiedSignedTransaction = VerifiedEnvelope<SenderSignedData, AuthoritySignInfo>;
2588
2589impl Transaction {
2590    pub fn verify_signature_for_testing(&self, verify_params: &VerifyParams) -> IotaResult {
2591        verify_sender_signed_data_message_signatures(self.data(), verify_params)
2592    }
2593
2594    pub fn try_into_verified_for_testing(
2595        self,
2596        verify_params: &VerifyParams,
2597    ) -> IotaResult<VerifiedTransaction> {
2598        self.verify_signature_for_testing(verify_params)?;
2599        Ok(VerifiedTransaction::new_from_verified(self))
2600    }
2601}
2602
2603impl SignedTransaction {
2604    pub fn verify_signatures_authenticated_for_testing(
2605        &self,
2606        committee: &Committee,
2607        verify_params: &VerifyParams,
2608    ) -> IotaResult {
2609        verify_sender_signed_data_message_signatures(self.data(), verify_params)?;
2610
2611        self.auth_sig().verify_secure(
2612            self.data(),
2613            Intent::iota_app(IntentScope::SenderSignedTransaction),
2614            committee,
2615        )
2616    }
2617
2618    pub fn try_into_verified_for_testing(
2619        self,
2620        committee: &Committee,
2621        verify_params: &VerifyParams,
2622    ) -> IotaResult<VerifiedSignedTransaction> {
2623        self.verify_signatures_authenticated_for_testing(committee, verify_params)?;
2624        Ok(VerifiedSignedTransaction::new_from_verified(self))
2625    }
2626}
2627
2628pub type CertifiedTransaction = Envelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
2629
2630impl CertifiedTransaction {
2631    pub fn certificate_digest(&self) -> CertificateDigest {
2632        let mut digest = DefaultHash::default();
2633        bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
2634        let hash = digest.finalize();
2635        CertificateDigest::new(hash.into())
2636    }
2637
2638    pub fn gas_price(&self) -> u64 {
2639        self.data().transaction_data().gas_price()
2640    }
2641
2642    // TODO: Eventually we should remove all calls to verify_signature
2643    // and make sure they all call verify to avoid repeated verifications.
2644    #[instrument(level = "trace", skip_all)]
2645    pub fn verify_signatures_authenticated(
2646        &self,
2647        committee: &Committee,
2648        verify_params: &VerifyParams,
2649    ) -> IotaResult {
2650        verify_sender_signed_data_message_signatures(self.data(), verify_params)?;
2651        self.auth_sig().verify_secure(
2652            self.data(),
2653            Intent::iota_app(IntentScope::SenderSignedTransaction),
2654            committee,
2655        )
2656    }
2657
2658    pub fn try_into_verified_for_testing(
2659        self,
2660        committee: &Committee,
2661        verify_params: &VerifyParams,
2662    ) -> IotaResult<VerifiedCertificate> {
2663        self.verify_signatures_authenticated(committee, verify_params)?;
2664        Ok(VerifiedCertificate::new_from_verified(self))
2665    }
2666
2667    pub fn verify_committee_sigs_only(&self, committee: &Committee) -> IotaResult {
2668        self.auth_sig().verify_secure(
2669            self.data(),
2670            Intent::iota_app(IntentScope::SenderSignedTransaction),
2671            committee,
2672        )
2673    }
2674}
2675
2676pub type VerifiedCertificate = VerifiedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
2677pub type TrustedCertificate = TrustedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
2678
2679#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
2680pub enum InputObjectKind {
2681    // A Move package, must be immutable.
2682    MovePackage(ObjectId),
2683    // A Move object, either immutable, or owned mutable.
2684    ImmOrOwnedMoveObject(ObjectRef),
2685    // A Move object that's shared and mutable.
2686    SharedMoveObject {
2687        id: ObjectId,
2688        initial_shared_version: SequenceNumber,
2689        mutable: bool,
2690    },
2691}
2692
2693impl InputObjectKind {
2694    pub fn object_id(&self) -> ObjectId {
2695        match self {
2696            Self::MovePackage(id) => *id,
2697            Self::ImmOrOwnedMoveObject(object_ref) => object_ref.object_id,
2698            Self::SharedMoveObject { id, .. } => *id,
2699        }
2700    }
2701
2702    pub fn version(&self) -> Option<SequenceNumber> {
2703        match self {
2704            Self::MovePackage(..) => None,
2705            Self::ImmOrOwnedMoveObject(object_ref) => Some(object_ref.version),
2706            Self::SharedMoveObject { .. } => None,
2707        }
2708    }
2709
2710    pub fn object_not_found_error(&self) -> UserInputError {
2711        match *self {
2712            Self::MovePackage(package_id) => {
2713                UserInputError::DependentPackageNotFound { package_id }
2714            }
2715            Self::ImmOrOwnedMoveObject(object_ref) => UserInputError::ObjectNotFound {
2716                object_id: object_ref.object_id,
2717                version: Some(object_ref.version),
2718            },
2719            Self::SharedMoveObject { id, .. } => UserInputError::ObjectNotFound {
2720                object_id: id,
2721                version: None,
2722            },
2723        }
2724    }
2725
2726    pub fn is_shared_object(&self) -> bool {
2727        matches!(self, Self::SharedMoveObject { .. })
2728    }
2729
2730    pub fn is_mutable(&self) -> bool {
2731        match self {
2732            Self::MovePackage(..) => false,
2733            Self::ImmOrOwnedMoveObject(_) => true,
2734            Self::SharedMoveObject { mutable, .. } => *mutable,
2735        }
2736    }
2737
2738    /// Merges another InputObjectKind into self.
2739    ///
2740    /// For shared objects, if either is mutable, the result is mutable. Fails
2741    /// if the IDs or initial versions do not match.
2742    /// For non-shared objects, fails if they are not equal.
2743    pub fn left_union_with_checks(&mut self, other: &InputObjectKind) -> UserInputResult<()> {
2744        match self {
2745            InputObjectKind::MovePackage(_) | InputObjectKind::ImmOrOwnedMoveObject(_) => {
2746                fp_ensure!(
2747                    self == other,
2748                    UserInputError::InconsistentInput {
2749                        object_id: other.object_id(),
2750                    }
2751                );
2752            }
2753            InputObjectKind::SharedMoveObject {
2754                id,
2755                initial_shared_version,
2756                mutable,
2757            } => match other {
2758                InputObjectKind::MovePackage(_) | InputObjectKind::ImmOrOwnedMoveObject(_) => {
2759                    fp_bail!(UserInputError::NotSharedObject)
2760                }
2761                InputObjectKind::SharedMoveObject {
2762                    id: other_id,
2763                    initial_shared_version: other_initial_shared_version,
2764                    mutable: other_mutable,
2765                } => {
2766                    fp_ensure!(id == other_id, UserInputError::SharedObjectIdMismatch);
2767                    fp_ensure!(
2768                        initial_shared_version == other_initial_shared_version,
2769                        UserInputError::SharedObjectStartingVersionMismatch
2770                    );
2771
2772                    if !*mutable && *other_mutable {
2773                        *mutable = *other_mutable;
2774                    }
2775                }
2776            },
2777        }
2778
2779        Ok(())
2780    }
2781
2782    /// Checks that `self` and `other` are equal for non-shared objects.
2783    /// For shared objects, checks that IDs and initial versions match while
2784    /// mutability can be different.
2785    pub fn check_consistency_for_authentication(
2786        &self,
2787        other: &InputObjectKind,
2788    ) -> UserInputResult<()> {
2789        match self {
2790            InputObjectKind::MovePackage(_) | InputObjectKind::ImmOrOwnedMoveObject(_) => {
2791                fp_ensure!(
2792                    self == other,
2793                    UserInputError::InconsistentInput {
2794                        object_id: self.object_id()
2795                    }
2796                );
2797            }
2798            InputObjectKind::SharedMoveObject {
2799                id,
2800                initial_shared_version,
2801                mutable: _,
2802            } => match other {
2803                InputObjectKind::MovePackage(_) | InputObjectKind::ImmOrOwnedMoveObject(_) => {
2804                    fp_bail!(UserInputError::InconsistentInput {
2805                        object_id: self.object_id()
2806                    })
2807                }
2808                InputObjectKind::SharedMoveObject {
2809                    id: other_id,
2810                    initial_shared_version: other_initial_shared_version,
2811                    mutable: _,
2812                } => {
2813                    fp_ensure!(
2814                        id == other_id,
2815                        UserInputError::InconsistentInput { object_id: *id }
2816                    );
2817                    fp_ensure!(
2818                        initial_shared_version == other_initial_shared_version,
2819                        UserInputError::InconsistentInput { object_id: *id }
2820                    );
2821                }
2822            },
2823        }
2824
2825        Ok(())
2826    }
2827}
2828
2829/// The result of reading an object for execution. Because shared objects may be
2830/// deleted, one possible result of reading a shared object is that
2831/// ObjectReadResultKind::Deleted is returned.
2832#[derive(Clone, Debug)]
2833pub struct ObjectReadResult {
2834    pub input_object_kind: InputObjectKind,
2835    pub object: ObjectReadResultKind,
2836}
2837
2838#[derive(Clone, PartialEq)]
2839pub enum ObjectReadResultKind {
2840    Object(Object),
2841    // The version of the object that the transaction intended to read, and the digest of the tx
2842    // that deleted it.
2843    DeletedSharedObject(SequenceNumber, TransactionDigest),
2844    // A shared object in a cancelled transaction. The sequence number embeds cancellation reason.
2845    CancelledTransactionSharedObject(SequenceNumber),
2846}
2847
2848impl std::fmt::Debug for ObjectReadResultKind {
2849    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2850        match self {
2851            ObjectReadResultKind::Object(obj) => {
2852                write!(f, "Object({:?})", obj.compute_object_reference())
2853            }
2854            ObjectReadResultKind::DeletedSharedObject(seq, digest) => {
2855                write!(f, "DeletedSharedObject({seq}, {digest})")
2856            }
2857            ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
2858                write!(f, "CancelledTransactionSharedObject({seq})")
2859            }
2860        }
2861    }
2862}
2863
2864impl From<Object> for ObjectReadResultKind {
2865    fn from(object: Object) -> Self {
2866        Self::Object(object)
2867    }
2868}
2869
2870impl ObjectReadResult {
2871    pub fn new(input_object_kind: InputObjectKind, object: ObjectReadResultKind) -> Self {
2872        if let (
2873            InputObjectKind::ImmOrOwnedMoveObject(_),
2874            ObjectReadResultKind::DeletedSharedObject(_, _),
2875        ) = (&input_object_kind, &object)
2876        {
2877            panic!("only shared objects can be DeletedSharedObject");
2878        }
2879
2880        if let (
2881            InputObjectKind::ImmOrOwnedMoveObject(_),
2882            ObjectReadResultKind::CancelledTransactionSharedObject(_),
2883        ) = (&input_object_kind, &object)
2884        {
2885            panic!("only shared objects can be CancelledTransactionSharedObject");
2886        }
2887
2888        Self {
2889            input_object_kind,
2890            object,
2891        }
2892    }
2893
2894    pub fn id(&self) -> ObjectId {
2895        self.input_object_kind.object_id()
2896    }
2897
2898    pub fn as_object(&self) -> Option<&Object> {
2899        match &self.object {
2900            ObjectReadResultKind::Object(object) => Some(object),
2901            ObjectReadResultKind::DeletedSharedObject(_, _) => None,
2902            ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
2903        }
2904    }
2905
2906    pub fn new_from_gas_object(gas: &Object) -> Self {
2907        let objref = gas.compute_object_reference();
2908        Self {
2909            input_object_kind: InputObjectKind::ImmOrOwnedMoveObject(objref),
2910            object: ObjectReadResultKind::Object(gas.clone()),
2911        }
2912    }
2913
2914    pub fn is_mutable(&self) -> bool {
2915        match (&self.input_object_kind, &self.object) {
2916            (InputObjectKind::MovePackage(_), _) => false,
2917            (InputObjectKind::ImmOrOwnedMoveObject(_), ObjectReadResultKind::Object(object)) => {
2918                !object.is_immutable()
2919            }
2920            (
2921                InputObjectKind::ImmOrOwnedMoveObject(_),
2922                ObjectReadResultKind::DeletedSharedObject(_, _),
2923            ) => unreachable!(),
2924            (
2925                InputObjectKind::ImmOrOwnedMoveObject(_),
2926                ObjectReadResultKind::CancelledTransactionSharedObject(_),
2927            ) => unreachable!(),
2928            (InputObjectKind::SharedMoveObject { mutable, .. }, _) => *mutable,
2929        }
2930    }
2931
2932    pub fn is_shared_object(&self) -> bool {
2933        self.input_object_kind.is_shared_object()
2934    }
2935
2936    pub fn is_deleted_shared_object(&self) -> bool {
2937        self.deletion_info().is_some()
2938    }
2939
2940    pub fn deletion_info(&self) -> Option<(SequenceNumber, TransactionDigest)> {
2941        match &self.object {
2942            ObjectReadResultKind::DeletedSharedObject(v, tx) => Some((*v, *tx)),
2943            _ => None,
2944        }
2945    }
2946
2947    /// Return the object ref iff the object is an owned object (i.e. not
2948    /// shared, not immutable).
2949    pub fn get_owned_objref(&self) -> Option<ObjectRef> {
2950        match (&self.input_object_kind, &self.object) {
2951            (InputObjectKind::MovePackage(_), _) => None,
2952            (
2953                InputObjectKind::ImmOrOwnedMoveObject(objref),
2954                ObjectReadResultKind::Object(object),
2955            ) => {
2956                if object.is_immutable() {
2957                    None
2958                } else {
2959                    Some(*objref)
2960                }
2961            }
2962            (
2963                InputObjectKind::ImmOrOwnedMoveObject(_),
2964                ObjectReadResultKind::DeletedSharedObject(_, _),
2965            ) => unreachable!(),
2966            (
2967                InputObjectKind::ImmOrOwnedMoveObject(_),
2968                ObjectReadResultKind::CancelledTransactionSharedObject(_),
2969            ) => unreachable!(),
2970            (InputObjectKind::SharedMoveObject { .. }, _) => None,
2971        }
2972    }
2973
2974    pub fn is_owned(&self) -> bool {
2975        self.get_owned_objref().is_some()
2976    }
2977
2978    pub fn to_shared_input(&self) -> Option<SharedInput> {
2979        match self.input_object_kind {
2980            InputObjectKind::MovePackage(_) => None,
2981            InputObjectKind::ImmOrOwnedMoveObject(_) => None,
2982            InputObjectKind::SharedMoveObject { id, mutable, .. } => Some(match &self.object {
2983                ObjectReadResultKind::Object(obj) => {
2984                    SharedInput::Existing(obj.compute_object_reference())
2985                }
2986                ObjectReadResultKind::DeletedSharedObject(seq, digest) => {
2987                    SharedInput::Deleted((id, *seq, mutable, *digest))
2988                }
2989                ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
2990                    SharedInput::Cancelled((id, *seq))
2991                }
2992            }),
2993        }
2994    }
2995
2996    pub fn get_previous_transaction(&self) -> Option<TransactionDigest> {
2997        match &self.object {
2998            ObjectReadResultKind::Object(obj) => Some(obj.previous_transaction),
2999            ObjectReadResultKind::DeletedSharedObject(_, digest) => Some(*digest),
3000            ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
3001        }
3002    }
3003}
3004
3005#[derive(Clone)]
3006pub struct InputObjects {
3007    objects: Vec<ObjectReadResult>,
3008}
3009
3010impl std::fmt::Debug for InputObjects {
3011    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3012        f.debug_list().entries(self.objects.iter()).finish()
3013    }
3014}
3015
3016// An InputObjects new-type that has been verified by iota-transaction-checks,
3017// and can be safely passed to execution.
3018pub struct CheckedInputObjects(InputObjects);
3019
3020// DO NOT CALL outside of iota-transaction-checks, genesis, or replay.
3021//
3022// CheckedInputObjects should really be defined in iota-transaction-checks so
3023// that we can make public construction impossible. But we can't do that because
3024// it would result in circular dependencies.
3025impl CheckedInputObjects {
3026    // Only called by iota-transaction-checks.
3027    pub fn new_with_checked_transaction_inputs(inputs: InputObjects) -> Self {
3028        Self(inputs)
3029    }
3030
3031    // Only called when building the genesis transaction
3032    pub fn new_for_genesis(input_objects: Vec<ObjectReadResult>) -> Self {
3033        Self(InputObjects::new(input_objects))
3034    }
3035
3036    // Only called from the replay tool.
3037    pub fn new_for_replay(input_objects: InputObjects) -> Self {
3038        Self(input_objects)
3039    }
3040
3041    pub fn inner(&self) -> &InputObjects {
3042        &self.0
3043    }
3044
3045    pub fn into_inner(self) -> InputObjects {
3046        self.0
3047    }
3048}
3049
3050impl From<Vec<ObjectReadResult>> for InputObjects {
3051    fn from(objects: Vec<ObjectReadResult>) -> Self {
3052        Self::new(objects)
3053    }
3054}
3055
3056impl InputObjects {
3057    pub fn new(objects: Vec<ObjectReadResult>) -> Self {
3058        Self { objects }
3059    }
3060
3061    pub fn len(&self) -> usize {
3062        self.objects.len()
3063    }
3064
3065    pub fn is_empty(&self) -> bool {
3066        self.objects.is_empty()
3067    }
3068
3069    pub fn contains_deleted_objects(&self) -> bool {
3070        self.objects
3071            .iter()
3072            .any(|obj| obj.is_deleted_shared_object())
3073    }
3074
3075    // Returns IDs of objects responsible for a transaction being cancelled, and the
3076    // corresponding reason for cancellation.
3077    pub fn get_cancelled_objects(&self) -> Option<(Vec<ObjectId>, SequenceNumber)> {
3078        let mut contains_cancelled = false;
3079        let mut cancel_reason = None;
3080        let mut cancelled_objects = Vec::new();
3081        for obj in &self.objects {
3082            if let ObjectReadResultKind::CancelledTransactionSharedObject(version) = obj.object {
3083                contains_cancelled = true;
3084                if version.is_congested() || version == SequenceNumber::RANDOMNESS_UNAVAILABLE {
3085                    // Verify we don't have multiple cancellation reasons.
3086                    assert!(cancel_reason.is_none() || cancel_reason == Some(version));
3087                    cancel_reason = Some(version);
3088                    cancelled_objects.push(obj.id());
3089                }
3090            }
3091        }
3092
3093        if !cancelled_objects.is_empty() {
3094            Some((
3095                cancelled_objects,
3096                cancel_reason
3097                    .expect("there should be a cancel reason if there are cancelled objects"),
3098            ))
3099        } else {
3100            assert!(!contains_cancelled);
3101            None
3102        }
3103    }
3104
3105    pub fn filter_owned_objects(&self) -> Vec<ObjectRef> {
3106        let owned_objects: Vec<_> = self
3107            .objects
3108            .iter()
3109            .filter_map(|obj| obj.get_owned_objref())
3110            .collect();
3111
3112        trace!(
3113            num_mutable_objects = owned_objects.len(),
3114            "Checked locks and found mutable objects"
3115        );
3116
3117        owned_objects
3118    }
3119
3120    pub fn filter_shared_objects(&self) -> Vec<SharedInput> {
3121        self.objects
3122            .iter()
3123            .filter(|obj| obj.is_shared_object())
3124            .map(|obj| {
3125                obj.to_shared_input()
3126                    .expect("already filtered for shared objects")
3127            })
3128            .collect()
3129    }
3130
3131    pub fn transaction_dependencies(&self) -> BTreeSet<TransactionDigest> {
3132        self.objects
3133            .iter()
3134            .filter_map(|obj| obj.get_previous_transaction())
3135            .collect()
3136    }
3137
3138    pub fn mutable_inputs(&self) -> BTreeMap<ObjectId, (VersionDigest, Owner)> {
3139        self.objects
3140            .iter()
3141            .filter_map(
3142                |ObjectReadResult {
3143                     input_object_kind,
3144                     object,
3145                 }| match (input_object_kind, object) {
3146                    (InputObjectKind::MovePackage(_), _) => None,
3147                    (
3148                        InputObjectKind::ImmOrOwnedMoveObject(object_ref),
3149                        ObjectReadResultKind::Object(object),
3150                    ) => {
3151                        if object.is_immutable() {
3152                            None
3153                        } else {
3154                            Some((
3155                                object_ref.object_id,
3156                                ((object_ref.version, object_ref.digest), object.owner),
3157                            ))
3158                        }
3159                    }
3160                    (
3161                        InputObjectKind::ImmOrOwnedMoveObject(_),
3162                        ObjectReadResultKind::DeletedSharedObject(_, _),
3163                    ) => {
3164                        unreachable!()
3165                    }
3166                    (
3167                        InputObjectKind::SharedMoveObject { .. },
3168                        ObjectReadResultKind::DeletedSharedObject(_, _),
3169                    ) => None,
3170                    (
3171                        InputObjectKind::SharedMoveObject { mutable, .. },
3172                        ObjectReadResultKind::Object(object),
3173                    ) => {
3174                        if *mutable {
3175                            let oref = object.compute_object_reference();
3176                            Some((oref.object_id, ((oref.version, oref.digest), object.owner)))
3177                        } else {
3178                            None
3179                        }
3180                    }
3181                    (
3182                        InputObjectKind::ImmOrOwnedMoveObject(_),
3183                        ObjectReadResultKind::CancelledTransactionSharedObject(_),
3184                    ) => {
3185                        unreachable!()
3186                    }
3187                    (
3188                        InputObjectKind::SharedMoveObject { .. },
3189                        ObjectReadResultKind::CancelledTransactionSharedObject(_),
3190                    ) => None,
3191                },
3192            )
3193            .collect()
3194    }
3195
3196    /// The version to set on objects created by the computation that `self` is
3197    /// input to. Guaranteed to be strictly greater than the versions of all
3198    /// input objects and objects received in the transaction.
3199    pub fn lamport_timestamp(&self, receiving_objects: &[ObjectRef]) -> SequenceNumber {
3200        let input_versions = self
3201            .objects
3202            .iter()
3203            .filter_map(|object| match &object.object {
3204                ObjectReadResultKind::Object(object) => {
3205                    object.data.as_struct_opt().map(MoveObject::version)
3206                }
3207                ObjectReadResultKind::DeletedSharedObject(v, _) => Some(*v),
3208                ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
3209            })
3210            .chain(
3211                receiving_objects
3212                    .iter()
3213                    .map(|object_ref| object_ref.version),
3214            );
3215
3216        SequenceNumber::lamport_increment(input_versions).unwrap()
3217    }
3218
3219    pub fn object_kinds(&self) -> impl Iterator<Item = &InputObjectKind> {
3220        self.objects.iter().map(
3221            |ObjectReadResult {
3222                 input_object_kind, ..
3223             }| input_object_kind,
3224        )
3225    }
3226
3227    pub fn into_object_map(self) -> BTreeMap<ObjectId, Object> {
3228        self.objects
3229            .into_iter()
3230            .filter_map(|o| o.as_object().map(|object| (o.id(), object.clone())))
3231            .collect()
3232    }
3233
3234    pub fn push(&mut self, object: ObjectReadResult) {
3235        self.objects.push(object);
3236    }
3237
3238    // If it contains then it returns the ObjectReadResult
3239    pub fn find_object_id_mut(&mut self, object_id: ObjectId) -> Option<&mut ObjectReadResult> {
3240        self.objects.iter_mut().find(|o| o.id() == object_id)
3241    }
3242
3243    pub fn iter(&self) -> impl Iterator<Item = &ObjectReadResult> {
3244        self.objects.iter()
3245    }
3246
3247    pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
3248        self.objects.iter().filter_map(|o| o.as_object())
3249    }
3250}
3251
3252// Result of attempting to read a receiving object (currently only at signing
3253// time). Because an object may have been previously received and deleted, the
3254// result may be ReceivingObjectReadResultKind::PreviouslyReceivedObject.
3255#[derive(Clone, Debug)]
3256pub enum ReceivingObjectReadResultKind {
3257    Object(Object),
3258    // The object was received by some other transaction, and we were not able to read it
3259    PreviouslyReceivedObject,
3260}
3261
3262impl ReceivingObjectReadResultKind {
3263    pub fn as_object(&self) -> Option<&Object> {
3264        match &self {
3265            Self::Object(object) => Some(object),
3266            Self::PreviouslyReceivedObject => None,
3267        }
3268    }
3269}
3270
3271pub struct ReceivingObjectReadResult {
3272    pub object_ref: ObjectRef,
3273    pub object: ReceivingObjectReadResultKind,
3274}
3275
3276impl ReceivingObjectReadResult {
3277    pub fn new(object_ref: ObjectRef, object: ReceivingObjectReadResultKind) -> Self {
3278        Self { object_ref, object }
3279    }
3280
3281    pub fn is_previously_received(&self) -> bool {
3282        matches!(
3283            self.object,
3284            ReceivingObjectReadResultKind::PreviouslyReceivedObject
3285        )
3286    }
3287}
3288
3289impl From<Object> for ReceivingObjectReadResultKind {
3290    fn from(object: Object) -> Self {
3291        Self::Object(object)
3292    }
3293}
3294
3295pub struct ReceivingObjects {
3296    pub objects: Vec<ReceivingObjectReadResult>,
3297}
3298
3299impl ReceivingObjects {
3300    pub fn iter(&self) -> impl Iterator<Item = &ReceivingObjectReadResult> {
3301        self.objects.iter()
3302    }
3303
3304    pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
3305        self.objects.iter().filter_map(|o| o.object.as_object())
3306    }
3307}
3308
3309impl From<Vec<ReceivingObjectReadResult>> for ReceivingObjects {
3310    fn from(objects: Vec<ReceivingObjectReadResult>) -> Self {
3311        Self { objects }
3312    }
3313}
3314
3315impl Display for CertifiedTransaction {
3316    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3317        let mut writer = String::new();
3318        writeln!(writer, "Transaction Hash: {:?}", self.digest())?;
3319        writeln!(
3320            writer,
3321            "Signed Authorities Bitmap : {:?}",
3322            self.auth_sig().signers_map
3323        )?;
3324        write!(writer, "{}", &self.data().intent_message().value.kind())?;
3325        write!(f, "{writer}")
3326    }
3327}
3328
3329/// TransactionKey uniquely identifies a transaction across all epochs.
3330/// Note that a single transaction may have multiple keys, for example a
3331/// RandomnessStateUpdate could be identified by both `Digest` and
3332/// `RandomnessRound`.
3333#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
3334pub enum TransactionKey {
3335    Digest(TransactionDigest),
3336    RandomnessRound(EpochId, RandomnessRound),
3337}
3338
3339impl TransactionKey {
3340    pub fn unwrap_digest(&self) -> &TransactionDigest {
3341        match self {
3342            TransactionKey::Digest(d) => d,
3343            _ => panic!("called expect_digest on a non-Digest TransactionKey: {self:?}"),
3344        }
3345    }
3346}
3347
3348/// Computes the auth digest for a single [`GenericSignature`].
3349///
3350/// For [`MoveAuthenticator`] signatures this equals
3351/// [`MoveAuthenticator::digest()`]. For all other supported signature types it
3352/// is the Blake2b256 of the serialized (flag-prefixed) signature bytes.
3353/// Returns an error for [`GenericSignature::ZkLoginAuthenticatorDeprecated`]
3354/// since zkLogin was never enabled on IOTA.
3355#[allow(deprecated)]
3356pub fn auth_digest_for_sig(sig: &GenericSignature) -> IotaResult<Digest> {
3357    match sig {
3358        GenericSignature::MoveAuthenticator(authenticator) => Ok(authenticator.digest()),
3359        GenericSignature::ZkLoginAuthenticatorDeprecated(_) => Err(IotaError::UnsupportedFeature {
3360            error: "zkLogin is not supported".to_string(),
3361        }),
3362        GenericSignature::MultiSig(_)
3363        | GenericSignature::Signature(_)
3364        | GenericSignature::PasskeyAuthenticator(_) => {
3365            let mut hasher = DefaultHash::default();
3366            hasher.update(sig.as_ref());
3367            Ok(Digest::new(hasher.finalize().into()))
3368        }
3369    }
3370}