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