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