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, etc.)
1016/// - **Queries**: inspecting transaction properties (shared objects, Move calls, sponsorship)
1017/// - **Validation**: checking transaction validity against protocol config
1018/// - **Constructors**: building new transactions (transfers, Move calls, programmable txs, etc.)
1019///
1020/// Note: The `iota-rust-sdk` crate (`iota-sdk-types`) defines additional
1021/// client-facing methods on [`Transaction`] itself.
1022pub trait TransactionAPI {
1023    /// Returns the address of the transaction sender.
1024    fn sender(&self) -> Address;
1025
1026    /// Returns a reference to the transaction kind.
1027    fn kind(&self) -> &TransactionKind;
1028
1029    /// Returns a mutable reference to the transaction kind.
1030    fn kind_mut(&mut self) -> &mut TransactionKind;
1031
1032    /// Consumes self and returns the transaction kind.
1033    fn into_kind(self) -> TransactionKind;
1034
1035    /// Returns the transaction signer(s). Includes both the sender and the gas
1036    /// owner if they differ (i.e. for sponsored transactions).
1037    fn signers(&self) -> NonEmpty<Address>;
1038
1039    /// Returns a reference to the gas data (owner, payment objects, price,
1040    /// budget).
1041    fn gas_data(&self) -> &GasPayment;
1042
1043    /// Returns the address that owns the gas payment objects.
1044    fn gas_owner(&self) -> Address;
1045
1046    /// Returns the gas payment object references.
1047    fn gas(&self) -> &[ObjectReference];
1048
1049    /// Returns the gas price for this transaction.
1050    fn gas_price(&self) -> u64;
1051
1052    /// Returns the gas budget for this transaction.
1053    fn gas_budget(&self) -> u64;
1054
1055    /// Returns the transaction expiration.
1056    fn expiration(&self) -> &TransactionExpiration;
1057
1058    /// Returns a list of the transaction data shared input objects.
1059    ///
1060    /// IMPORTANT: This function does not return shared objects associated with
1061    /// `MoveAuthenticator` signatures. To check those objects as well, use the
1062    /// corresponding function from `SenderSignedTransaction`.
1063    fn shared_input_objects(&self) -> Vec<SharedObjectReference>;
1064
1065    /// Returns a list of Move calls as `(package_id, module_name,
1066    /// function_name)` tuples.
1067    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)>;
1068
1069    /// Returns the input objects required by the transaction body.
1070    ///
1071    /// Note: this does NOT include the objects read by any `MoveAuthenticator`s
1072    /// (abstract-account authenticators); those are carried by the transaction
1073    /// envelope, not by `TransactionData`. For the full set including
1074    /// authenticator inputs, use `SenderSignedData::input_objects` or
1075    /// `SenderSignedData::collect_all_input_object_kind_for_reading`.
1076    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
1077
1078    /// Returns object references for all objects being received in this
1079    /// transaction.
1080    fn receiving_objects(&self) -> Vec<ObjectReference>;
1081
1082    /// Validates the transaction data against the given protocol config,
1083    /// including gas checks.
1084    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
1085
1086    /// Validates the transaction data against the given protocol config,
1087    /// skipping gas-related checks.
1088    fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult;
1089
1090    /// Checks the BCS size of the transaction data against the protocol's
1091    /// `max_tx_size_bytes`.
1092    fn check_serialized_size(&self, config: &ProtocolConfig) -> IotaResult;
1093
1094    /// Checks the gas payment against the protocol's cap on how many objects it
1095    /// may name.
1096    fn check_gas_payment_size(&self, config: &ProtocolConfig) -> UserInputResult;
1097
1098    /// Check if the transaction is compliant with sponsorship.
1099    fn check_sponsorship(&self) -> UserInputResult;
1100
1101    /// Returns `true` if this is a system transaction.
1102    fn is_system_tx(&self) -> bool;
1103    /// Returns `true` if this is the genesis transaction.
1104    fn is_genesis_tx(&self) -> bool;
1105
1106    /// returns true if the transaction is one that is specially sequenced to
1107    /// run at the very end of the epoch
1108    fn is_end_of_epoch_tx(&self) -> bool;
1109
1110    /// Check if the transaction is sponsored (namely gas owner != sender)
1111    fn is_sponsored_tx(&self) -> bool;
1112
1113    /// Returns a mutable reference to the sender address. **Testing only.**
1114    fn sender_mut_for_testing(&mut self) -> &mut Address;
1115
1116    /// Returns a mutable reference to the gas data.
1117    fn gas_data_mut(&mut self) -> &mut GasPayment;
1118
1119    /// Returns a mutable reference to the expiration. **Testing only.**
1120    fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration;
1121
1122    /// Creates a new system transaction with no gas payment. Used for
1123    /// validator-initiated transactions (epoch changes, checkpoints, etc.).
1124    fn new_system_transaction(kind: TransactionKind) -> Transaction;
1125
1126    /// Creates a new transaction with a single gas payment coin. The sender
1127    /// is also the gas owner.
1128    #[allow(clippy::new_ret_no_self)]
1129    fn new(
1130        kind: TransactionKind,
1131        sender: Address,
1132        gas_payment: ObjectReference,
1133        gas_budget: u64,
1134        gas_price: u64,
1135    ) -> Transaction;
1136
1137    /// Creates a new transaction with multiple gas payment coins. The sender
1138    /// is also the gas owner.
1139    fn new_with_gas_coins(
1140        kind: TransactionKind,
1141        sender: Address,
1142        gas_payment: Vec<ObjectReference>,
1143        gas_budget: u64,
1144        gas_price: u64,
1145    ) -> Transaction;
1146
1147    /// Creates a new transaction with multiple gas payment coins and a
1148    /// separate gas sponsor. Use this for sponsored transactions where
1149    /// the gas owner differs from the sender.
1150    fn new_with_gas_coins_allow_sponsor(
1151        kind: TransactionKind,
1152        sender: Address,
1153        gas_payment: Vec<ObjectReference>,
1154        gas_budget: u64,
1155        gas_price: u64,
1156        gas_sponsor: Address,
1157    ) -> Transaction;
1158
1159    /// Creates a new transaction from a pre-built [`GasPayment`] struct.
1160    fn new_with_gas_data(
1161        kind: TransactionKind,
1162        sender: Address,
1163        gas_data: GasPayment,
1164    ) -> Transaction;
1165
1166    /// Creates a transaction that calls a single Move function with a single
1167    /// gas payment coin.
1168    fn new_move_call(
1169        sender: Address,
1170        package: ObjectId,
1171        module: Identifier,
1172        function: Identifier,
1173        type_arguments: Vec<TypeTag>,
1174        gas_payment: ObjectReference,
1175        arguments: Vec<CallArg>,
1176        gas_budget: u64,
1177        gas_price: u64,
1178    ) -> anyhow::Result<Transaction>;
1179
1180    /// Creates a transaction that calls a single Move function with multiple
1181    /// gas payment coins.
1182    fn new_move_call_with_gas_coins(
1183        sender: Address,
1184        package: ObjectId,
1185        module: Identifier,
1186        function: Identifier,
1187        type_arguments: Vec<TypeTag>,
1188        gas_payment: Vec<ObjectReference>,
1189        arguments: Vec<CallArg>,
1190        gas_budget: u64,
1191        gas_price: u64,
1192    ) -> anyhow::Result<Transaction>;
1193
1194    /// Creates a transaction that transfers an object to a recipient.
1195    fn new_transfer(
1196        recipient: Address,
1197        object_ref: ObjectReference,
1198        sender: Address,
1199        gas_payment: ObjectReference,
1200        gas_budget: u64,
1201        gas_price: u64,
1202    ) -> Transaction;
1203
1204    /// Creates a transaction that transfers IOTA coins to a recipient.
1205    /// If `amount` is `None`, the entire gas coin balance (minus gas fees)
1206    /// is transferred.
1207    fn new_transfer_iota(
1208        recipient: Address,
1209        sender: Address,
1210        amount: Option<u64>,
1211        gas_payment: ObjectReference,
1212        gas_budget: u64,
1213        gas_price: u64,
1214    ) -> Transaction;
1215
1216    /// Creates a sponsored transaction that transfers IOTA coins to a
1217    /// recipient. If `amount` is `None`, the entire gas coin balance
1218    /// (minus gas fees) is transferred.
1219    fn new_transfer_iota_allow_sponsor(
1220        recipient: Address,
1221        sender: Address,
1222        amount: Option<u64>,
1223        gas_payment: ObjectReference,
1224        gas_budget: u64,
1225        gas_price: u64,
1226        gas_sponsor: Address,
1227    ) -> Transaction;
1228
1229    /// Creates a transaction that pays multiple recipients from a set of
1230    /// input coins. The coins are merged and then split to satisfy the
1231    /// specified amounts.
1232    fn new_pay(
1233        sender: Address,
1234        coins: Vec<ObjectReference>,
1235        recipients: Vec<Address>,
1236        amounts: Vec<u64>,
1237        gas_payment: ObjectReference,
1238        gas_budget: u64,
1239        gas_price: u64,
1240    ) -> anyhow::Result<Transaction>;
1241
1242    /// Creates a transaction that pays multiple recipients using IOTA coins.
1243    /// Similar to [`Self::new_pay`] but the gas coin is also used as an
1244    /// input coin.
1245    fn new_pay_iota(
1246        sender: Address,
1247        coins: Vec<ObjectReference>,
1248        recipients: Vec<Address>,
1249        amounts: Vec<u64>,
1250        gas_payment: ObjectReference,
1251        gas_budget: u64,
1252        gas_price: u64,
1253    ) -> anyhow::Result<Transaction>;
1254
1255    /// Creates a transaction that sends all IOTA from the given coins to a
1256    /// single recipient. The gas coin is included as an input coin.
1257    fn new_pay_all_iota(
1258        sender: Address,
1259        coins: Vec<ObjectReference>,
1260        recipient: Address,
1261        gas_payment: ObjectReference,
1262        gas_budget: u64,
1263        gas_price: u64,
1264    ) -> Transaction;
1265
1266    /// Creates a transaction that splits a coin into multiple coins with the
1267    /// specified amounts.
1268    fn new_split_coin(
1269        sender: Address,
1270        coin: ObjectReference,
1271        amounts: Vec<u64>,
1272        gas_payment: ObjectReference,
1273        gas_budget: u64,
1274        gas_price: u64,
1275    ) -> Transaction;
1276
1277    /// Creates a transaction that publishes new Move modules.
1278    fn new_module(
1279        sender: Address,
1280        gas_payment: ObjectReference,
1281        modules: Vec<Vec<u8>>,
1282        dep_ids: Vec<ObjectId>,
1283        gas_budget: u64,
1284        gas_price: u64,
1285    ) -> Transaction;
1286
1287    /// Creates a transaction that upgrades an existing Move package.
1288    /// Requires the upgrade capability object and the upgrade policy.
1289    fn new_upgrade(
1290        sender: Address,
1291        gas_payment: ObjectReference,
1292        package_id: ObjectId,
1293        modules: Vec<Vec<u8>>,
1294        dep_ids: Vec<ObjectId>,
1295        upgrade_capability_and_owner: (ObjectReference, Owner),
1296        upgrade_policy: u8,
1297        digest: Vec<u8>,
1298        gas_budget: u64,
1299        gas_price: u64,
1300    ) -> anyhow::Result<Transaction>;
1301
1302    /// Creates a programmable transaction with multiple gas payment coins.
1303    /// The sender is also the gas owner.
1304    fn new_programmable(
1305        sender: Address,
1306        gas_payment: Vec<ObjectReference>,
1307        pt: ProgrammableTransaction,
1308        gas_budget: u64,
1309        gas_price: u64,
1310    ) -> Transaction;
1311
1312    /// Creates a programmable transaction with multiple gas payment coins
1313    /// and a separate gas sponsor.
1314    fn new_programmable_allow_sponsor(
1315        sender: Address,
1316        gas_payment: Vec<ObjectReference>,
1317        pt: ProgrammableTransaction,
1318        gas_budget: u64,
1319        gas_price: u64,
1320        sponsor: Address,
1321    ) -> Transaction;
1322
1323    /// Returns the internal message version number.
1324    fn message_version(&self) -> u64;
1325
1326    /// Consumes self and returns the transaction kind, sender address, and
1327    /// gas payment object references as a tuple.
1328    fn execution_parts(&self) -> (TransactionKind, Address, GasPayment);
1329}
1330
1331fn tx_bcs_size<T: Serialize>(tx: &T) -> IotaResult<usize> {
1332    bcs::serialized_size(tx).map_err(|e| IotaError::TransactionSerialization {
1333        error: e.to_string(),
1334    })
1335}
1336
1337/// Checks the BCS size of `transaction` against the protocol's
1338/// `max_tx_size_bytes` and returns it.
1339fn check_transaction_size<T: Serialize>(
1340    transaction: &T,
1341    config: &ProtocolConfig,
1342) -> IotaResult<usize> {
1343    let tx_size = tx_bcs_size(transaction)?;
1344    let max_tx_size_bytes = config.max_tx_size_bytes();
1345    fp_ensure!(
1346        tx_size as u64 <= max_tx_size_bytes,
1347        IotaError::UserInput {
1348            error: UserInputError::SizeLimitExceeded {
1349                limit: format!(
1350                    "serialized transaction size exceeded maximum of {max_tx_size_bytes}"
1351                ),
1352                value: tx_size.to_string(),
1353            }
1354        }
1355    );
1356    Ok(tx_size)
1357}
1358
1359impl TransactionAPI for Transaction {
1360    fn check_serialized_size(&self, config: &ProtocolConfig) -> IotaResult {
1361        check_transaction_size(self, config).map(|_| ())
1362    }
1363
1364    fn sender(&self) -> Address {
1365        match self {
1366            Self::V1(v1) => v1.sender,
1367            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1368        }
1369    }
1370
1371    fn kind(&self) -> &TransactionKind {
1372        match self {
1373            Self::V1(v1) => &v1.kind,
1374            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1375        }
1376    }
1377
1378    fn kind_mut(&mut self) -> &mut TransactionKind {
1379        match self {
1380            Self::V1(v1) => &mut v1.kind,
1381            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1382        }
1383    }
1384
1385    fn into_kind(self) -> TransactionKind {
1386        match self {
1387            Self::V1(v1) => v1.kind,
1388            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1389        }
1390    }
1391
1392    fn signers(&self) -> NonEmpty<Address> {
1393        let mut signers = nonempty![self.sender()];
1394        if self.gas_owner() != self.sender() {
1395            signers.push(self.gas_owner());
1396        }
1397        signers
1398    }
1399
1400    fn gas_data(&self) -> &GasPayment {
1401        match self {
1402            Self::V1(v1) => &v1.gas_payment,
1403            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1404        }
1405    }
1406
1407    fn gas_owner(&self) -> Address {
1408        self.gas_data().owner
1409    }
1410
1411    fn gas(&self) -> &[ObjectReference] {
1412        &self.gas_data().objects
1413    }
1414
1415    fn gas_price(&self) -> u64 {
1416        self.gas_data().price
1417    }
1418
1419    fn gas_budget(&self) -> u64 {
1420        self.gas_data().budget
1421    }
1422
1423    fn expiration(&self) -> &TransactionExpiration {
1424        match self {
1425            Self::V1(v1) => &v1.expiration,
1426            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1427        }
1428    }
1429
1430    fn shared_input_objects(&self) -> Vec<SharedObjectReference> {
1431        self.kind().shared_input_objects().collect()
1432    }
1433
1434    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)> {
1435        self.kind().move_calls()
1436    }
1437
1438    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
1439        let mut inputs = self.kind().input_objects()?;
1440
1441        if !self.kind().is_system() {
1442            inputs.extend(
1443                self.gas()
1444                    .iter()
1445                    .map(|obj_ref| InputObjectKind::ImmOrOwnedMoveObject(*obj_ref)),
1446            );
1447        }
1448        Ok(inputs)
1449    }
1450
1451    fn receiving_objects(&self) -> Vec<ObjectReference> {
1452        self.kind().receiving_objects()
1453    }
1454
1455    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1456        fp_ensure!(!self.gas().is_empty(), UserInputError::MissingGasPayment);
1457        self.check_gas_payment_size(config)?;
1458        if config.validate_input_object_versions() {
1459            for gas_object in self.gas() {
1460                input_object_version_validity_check(gas_object.version)?;
1461            }
1462        }
1463        self.validity_check_no_gas_check(config)
1464    }
1465
1466    #[instrument(level = "trace", skip_all)]
1467    fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult {
1468        self.kind().validity_check(config)?;
1469        self.check_sponsorship()
1470    }
1471
1472    fn check_gas_payment_size(&self, config: &ProtocolConfig) -> UserInputResult {
1473        fp_ensure!(
1474            self.gas().len() < config.max_gas_payment_objects() as usize,
1475            UserInputError::SizeLimitExceeded {
1476                limit: "maximum number of gas payment objects".to_string(),
1477                value: config.max_gas_payment_objects().to_string()
1478            }
1479        );
1480        Ok(())
1481    }
1482
1483    fn is_sponsored_tx(&self) -> bool {
1484        self.gas_owner() != self.sender()
1485    }
1486
1487    fn check_sponsorship(&self) -> UserInputResult {
1488        if self.gas_owner() == self.sender() {
1489            return Ok(());
1490        }
1491        if matches!(self.kind(), TransactionKind::Programmable(_)) {
1492            return Ok(());
1493        }
1494        Err(UserInputError::UnsupportedSponsoredTransactionKind)
1495    }
1496
1497    fn is_end_of_epoch_tx(&self) -> bool {
1498        matches!(self.kind(), TransactionKind::EndOfEpoch(_))
1499    }
1500
1501    fn is_system_tx(&self) -> bool {
1502        self.kind().is_system()
1503    }
1504
1505    fn is_genesis_tx(&self) -> bool {
1506        matches!(self.kind(), TransactionKind::Genesis(_))
1507    }
1508
1509    fn sender_mut_for_testing(&mut self) -> &mut Address {
1510        match self {
1511            Self::V1(v1) => &mut v1.sender,
1512            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1513        }
1514    }
1515
1516    fn gas_data_mut(&mut self) -> &mut GasPayment {
1517        match self {
1518            Self::V1(v1) => &mut v1.gas_payment,
1519            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1520        }
1521    }
1522
1523    fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration {
1524        match self {
1525            Self::V1(v1) => &mut v1.expiration,
1526            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1527        }
1528    }
1529
1530    fn new_system_transaction(kind: TransactionKind) -> Transaction {
1531        assert!(kind.is_system());
1532        let sender = Address::ZERO;
1533        Transaction::V1(TransactionV1 {
1534            kind,
1535            sender,
1536            gas_payment: GasPayment {
1537                price: GAS_PRICE_FOR_SYSTEM_TX,
1538                owner: sender,
1539                objects: vec![ObjectReference::new(
1540                    ObjectId::ZERO,
1541                    Version::default(),
1542                    ObjectDigest::MIN,
1543                )],
1544                budget: 0,
1545            },
1546            expiration: TransactionExpiration::None,
1547        })
1548    }
1549
1550    fn new(
1551        kind: TransactionKind,
1552        sender: Address,
1553        gas_payment: ObjectReference,
1554        gas_budget: u64,
1555        gas_price: u64,
1556    ) -> Transaction {
1557        Transaction::V1(TransactionV1 {
1558            kind,
1559            sender,
1560            gas_payment: GasPayment {
1561                price: gas_price,
1562                owner: sender,
1563                objects: vec![gas_payment],
1564                budget: gas_budget,
1565            },
1566            expiration: TransactionExpiration::None,
1567        })
1568    }
1569
1570    fn new_with_gas_coins(
1571        kind: TransactionKind,
1572        sender: Address,
1573        gas_payment: Vec<ObjectReference>,
1574        gas_budget: u64,
1575        gas_price: u64,
1576    ) -> Transaction {
1577        Transaction::new_with_gas_coins_allow_sponsor(
1578            kind,
1579            sender,
1580            gas_payment,
1581            gas_budget,
1582            gas_price,
1583            sender,
1584        )
1585    }
1586
1587    fn new_with_gas_coins_allow_sponsor(
1588        kind: TransactionKind,
1589        sender: Address,
1590        gas_payment: Vec<ObjectReference>,
1591        gas_budget: u64,
1592        gas_price: u64,
1593        gas_sponsor: Address,
1594    ) -> Transaction {
1595        Transaction::V1(TransactionV1 {
1596            kind,
1597            sender,
1598            gas_payment: GasPayment {
1599                price: gas_price,
1600                owner: gas_sponsor,
1601                objects: gas_payment,
1602                budget: gas_budget,
1603            },
1604            expiration: TransactionExpiration::None,
1605        })
1606    }
1607
1608    fn new_with_gas_data(
1609        kind: TransactionKind,
1610        sender: Address,
1611        gas_data: GasPayment,
1612    ) -> Transaction {
1613        Transaction::V1(TransactionV1 {
1614            kind,
1615            sender,
1616            gas_payment: gas_data,
1617            expiration: TransactionExpiration::None,
1618        })
1619    }
1620
1621    fn new_move_call(
1622        sender: Address,
1623        package: ObjectId,
1624        module: Identifier,
1625        function: Identifier,
1626        type_arguments: Vec<TypeTag>,
1627        gas_payment: ObjectReference,
1628        arguments: Vec<CallArg>,
1629        gas_budget: u64,
1630        gas_price: u64,
1631    ) -> anyhow::Result<Transaction> {
1632        Transaction::new_move_call_with_gas_coins(
1633            sender,
1634            package,
1635            module,
1636            function,
1637            type_arguments,
1638            vec![gas_payment],
1639            arguments,
1640            gas_budget,
1641            gas_price,
1642        )
1643    }
1644
1645    fn new_move_call_with_gas_coins(
1646        sender: Address,
1647        package: ObjectId,
1648        module: Identifier,
1649        function: Identifier,
1650        type_arguments: Vec<TypeTag>,
1651        gas_payment: Vec<ObjectReference>,
1652        arguments: Vec<CallArg>,
1653        gas_budget: u64,
1654        gas_price: u64,
1655    ) -> anyhow::Result<Transaction> {
1656        let pt = {
1657            let mut builder = ProgrammableTransactionBuilder::new();
1658            builder.move_call(package, module, function, type_arguments, arguments)?;
1659            builder.finish()
1660        };
1661        Ok(Transaction::new_programmable(
1662            sender,
1663            gas_payment,
1664            pt,
1665            gas_budget,
1666            gas_price,
1667        ))
1668    }
1669
1670    fn new_transfer(
1671        recipient: Address,
1672        object_ref: ObjectReference,
1673        sender: Address,
1674        gas_payment: ObjectReference,
1675        gas_budget: u64,
1676        gas_price: u64,
1677    ) -> Transaction {
1678        let pt = {
1679            let mut builder = ProgrammableTransactionBuilder::new();
1680            builder.transfer_object(recipient, object_ref).unwrap();
1681            builder.finish()
1682        };
1683        Transaction::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
1684    }
1685
1686    fn new_transfer_iota(
1687        recipient: Address,
1688        sender: Address,
1689        amount: Option<u64>,
1690        gas_payment: ObjectReference,
1691        gas_budget: u64,
1692        gas_price: u64,
1693    ) -> Transaction {
1694        Transaction::new_transfer_iota_allow_sponsor(
1695            recipient,
1696            sender,
1697            amount,
1698            gas_payment,
1699            gas_budget,
1700            gas_price,
1701            sender,
1702        )
1703    }
1704
1705    fn new_transfer_iota_allow_sponsor(
1706        recipient: Address,
1707        sender: Address,
1708        amount: Option<u64>,
1709        gas_payment: ObjectReference,
1710        gas_budget: u64,
1711        gas_price: u64,
1712        gas_sponsor: Address,
1713    ) -> Transaction {
1714        let pt = {
1715            let mut builder = ProgrammableTransactionBuilder::new();
1716            builder.transfer_iota(recipient, amount);
1717            builder.finish()
1718        };
1719        Transaction::new_programmable_allow_sponsor(
1720            sender,
1721            vec![gas_payment],
1722            pt,
1723            gas_budget,
1724            gas_price,
1725            gas_sponsor,
1726        )
1727    }
1728
1729    fn new_pay(
1730        sender: Address,
1731        coins: Vec<ObjectReference>,
1732        recipients: Vec<Address>,
1733        amounts: Vec<u64>,
1734        gas_payment: ObjectReference,
1735        gas_budget: u64,
1736        gas_price: u64,
1737    ) -> anyhow::Result<Transaction> {
1738        let pt = {
1739            let mut builder = ProgrammableTransactionBuilder::new();
1740            builder.pay(coins, recipients, amounts)?;
1741            builder.finish()
1742        };
1743        Ok(Transaction::new_programmable(
1744            sender,
1745            vec![gas_payment],
1746            pt,
1747            gas_budget,
1748            gas_price,
1749        ))
1750    }
1751
1752    fn new_pay_iota(
1753        sender: Address,
1754        mut coins: Vec<ObjectReference>,
1755        recipients: Vec<Address>,
1756        amounts: Vec<u64>,
1757        gas_payment: ObjectReference,
1758        gas_budget: u64,
1759        gas_price: u64,
1760    ) -> anyhow::Result<Transaction> {
1761        coins.insert(0, gas_payment);
1762        let pt = {
1763            let mut builder = ProgrammableTransactionBuilder::new();
1764            builder.pay_iota(recipients, amounts)?;
1765            builder.finish()
1766        };
1767        Ok(Transaction::new_programmable(
1768            sender, coins, pt, gas_budget, gas_price,
1769        ))
1770    }
1771
1772    fn new_pay_all_iota(
1773        sender: Address,
1774        mut coins: Vec<ObjectReference>,
1775        recipient: Address,
1776        gas_payment: ObjectReference,
1777        gas_budget: u64,
1778        gas_price: u64,
1779    ) -> Transaction {
1780        coins.insert(0, gas_payment);
1781        let pt = {
1782            let mut builder = ProgrammableTransactionBuilder::new();
1783            builder.pay_all_iota(recipient);
1784            builder.finish()
1785        };
1786        Transaction::new_programmable(sender, coins, pt, gas_budget, gas_price)
1787    }
1788
1789    fn new_split_coin(
1790        sender: Address,
1791        coin: ObjectReference,
1792        amounts: Vec<u64>,
1793        gas_payment: ObjectReference,
1794        gas_budget: u64,
1795        gas_price: u64,
1796    ) -> Transaction {
1797        let pt = {
1798            let mut builder = ProgrammableTransactionBuilder::new();
1799            builder.split_coin(sender, coin, amounts);
1800            builder.finish()
1801        };
1802        Transaction::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
1803    }
1804
1805    fn new_module(
1806        sender: Address,
1807        gas_payment: ObjectReference,
1808        modules: Vec<Vec<u8>>,
1809        dep_ids: Vec<ObjectId>,
1810        gas_budget: u64,
1811        gas_price: u64,
1812    ) -> Transaction {
1813        let pt = {
1814            let mut builder = ProgrammableTransactionBuilder::new();
1815            let upgrade_cap = builder.publish_upgradeable(modules, dep_ids);
1816            builder.transfer_arg(sender, upgrade_cap);
1817            builder.finish()
1818        };
1819        Transaction::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
1820    }
1821
1822    fn new_upgrade(
1823        sender: Address,
1824        gas_payment: ObjectReference,
1825        package_id: ObjectId,
1826        modules: Vec<Vec<u8>>,
1827        dep_ids: Vec<ObjectId>,
1828        (upgrade_capability, capability_owner): (ObjectReference, Owner),
1829        upgrade_policy: u8,
1830        digest: Vec<u8>,
1831        gas_budget: u64,
1832        gas_price: u64,
1833    ) -> anyhow::Result<Transaction> {
1834        let pt = {
1835            let mut builder = ProgrammableTransactionBuilder::new();
1836            let capability_arg = match capability_owner {
1837                Owner::Address(_) => CallArg::ImmutableOrOwned(upgrade_capability),
1838                Owner::Shared(initial_shared_version) => {
1839                    CallArg::Shared(SharedObjectReference::new(
1840                        upgrade_capability.object_id,
1841                        initial_shared_version,
1842                        true,
1843                    ))
1844                }
1845                Owner::Immutable => {
1846                    bail!("Upgrade capability is stored immutably and cannot be used for upgrades");
1847                }
1848                Owner::Object(_) => {
1849                    bail!("Upgrade capability controlled by object");
1850                }
1851                _ => unimplemented!("a new Owner enum variant was added and needs to be handled"),
1852            };
1853            builder.obj(capability_arg).unwrap();
1854            let upgrade_arg = builder.pure(upgrade_policy).unwrap();
1855            let digest_arg = builder.pure(digest).unwrap();
1856            let upgrade_ticket = builder.programmable_move_call(
1857                ObjectId::FRAMEWORK,
1858                Identifier::PACKAGE_MODULE,
1859                Identifier::from_static("authorize_upgrade"),
1860                vec![],
1861                vec![Argument::Input(0), upgrade_arg, digest_arg],
1862            );
1863            let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
1864
1865            builder.programmable_move_call(
1866                ObjectId::FRAMEWORK,
1867                Identifier::PACKAGE_MODULE,
1868                Identifier::from_static("commit_upgrade"),
1869                vec![],
1870                vec![Argument::Input(0), upgrade_receipt],
1871            );
1872
1873            builder.finish()
1874        };
1875        Ok(Transaction::new_programmable(
1876            sender,
1877            vec![gas_payment],
1878            pt,
1879            gas_budget,
1880            gas_price,
1881        ))
1882    }
1883
1884    fn new_programmable(
1885        sender: Address,
1886        gas_payment: Vec<ObjectReference>,
1887        pt: ProgrammableTransaction,
1888        gas_budget: u64,
1889        gas_price: u64,
1890    ) -> Transaction {
1891        Transaction::new_programmable_allow_sponsor(
1892            sender,
1893            gas_payment,
1894            pt,
1895            gas_budget,
1896            gas_price,
1897            sender,
1898        )
1899    }
1900
1901    fn new_programmable_allow_sponsor(
1902        sender: Address,
1903        gas_payment: Vec<ObjectReference>,
1904        pt: ProgrammableTransaction,
1905        gas_budget: u64,
1906        gas_price: u64,
1907        sponsor: Address,
1908    ) -> Transaction {
1909        let kind = TransactionKind::Programmable(pt);
1910        Transaction::new_with_gas_coins_allow_sponsor(
1911            kind,
1912            sender,
1913            gas_payment,
1914            gas_budget,
1915            gas_price,
1916            sponsor,
1917        )
1918    }
1919
1920    fn message_version(&self) -> u64 {
1921        match self {
1922            Transaction::V1(_) => 1,
1923            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1924        }
1925    }
1926
1927    fn execution_parts(&self) -> (TransactionKind, Address, GasPayment) {
1928        (self.kind().clone(), self.sender(), self.gas_data().clone())
1929    }
1930}
1931
1932pub struct TxValidityCheckContext<'a> {
1933    pub config: &'a ProtocolConfig,
1934    pub epoch: EpochId,
1935}
1936
1937/// Merge every [`MoveAuthenticator`]'s input objects into `input_objects`.
1938///
1939/// Objects not yet present are appended; for an object that appears in both
1940/// sets the kinds are checked for consistency and unioned via
1941/// [`InputObjectKind::left_union_with_checks`] (in particular, a shared object
1942/// may differ in mutability but not in initial shared version).
1943pub fn merge_authenticator_input_objects<'a>(
1944    move_authenticators: impl IntoIterator<Item = &'a MoveAuthenticator>,
1945    input_objects: &mut Vec<InputObjectKind>,
1946) -> UserInputResult<()> {
1947    for move_authenticator in move_authenticators {
1948        for auth_object in move_authenticator.input_objects() {
1949            let entry = input_objects
1950                .iter_mut()
1951                .find(|o| o.object_id() == auth_object.object_id());
1952
1953            match entry {
1954                None => input_objects.push(auth_object),
1955                Some(existing) => existing.left_union_with_checks(&auth_object)?,
1956            }
1957        }
1958    }
1959    Ok(())
1960}
1961
1962/// API for accessing and constructing [`SenderSignedTransaction`].
1963///
1964/// This trait provides node-internal methods on the SDK's
1965/// [`SenderSignedTransaction`], which carries the transaction data together
1966/// with the signatures of all transaction participants. A non-participant
1967/// signature must not be present, and the signature order does not matter.
1968pub trait SenderSignedTransactionAPI {
1969    /// Creates a new [`SenderSignedTransaction`] with a single sender
1970    /// signature.
1971    fn new_from_sender_signature(
1972        tx: Transaction,
1973        tx_signature: SimpleSignature,
1974    ) -> SenderSignedTransaction;
1975
1976    /// Adds a signature. Does not check the validity of the signature or
1977    /// perform any de-dup checks.
1978    fn add_signature(&mut self, new_signature: SimpleSignature);
1979
1980    /// Returns a mapping from the address each signature commits to, to the
1981    /// signature itself.
1982    fn get_signer_sig_mapping(&self) -> IotaResult<BTreeMap<Address, &UserSignature>>;
1983
1984    /// Returns `true` if any signature is a multisig.
1985    fn has_multisig(&self) -> bool;
1986
1987    /// Returns a mutable reference to the transaction. **Testing only.**
1988    fn transaction_mut_for_testing(&mut self) -> &mut Transaction;
1989
1990    /// Returns a mutable reference to the signatures. **Testing only.**
1991    fn tx_signatures_mut_for_testing(&mut self) -> &mut Vec<UserSignature>;
1992
1993    /// Returns the BCS serialized size in bytes.
1994    fn serialized_size(&self) -> IotaResult<usize>;
1995
1996    /// Validate untrusted user transaction, including its size, input count,
1997    /// command count, etc.
1998    /// Returns the certificate serialised bytes size.
1999    fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> Result<usize, IotaError>;
2000
2001    /// Returns all unique input objects including those from
2002    /// `MoveAuthenticator`s if any for reading.
2003    ///
2004    /// Although some shared objects(with a different mutability flag, for
2005    /// example) can be duplicated in the transaction and authenticators, we
2006    /// load them independently to make it possible to analyze the inputs in
2007    /// the transaction checkers.
2008    fn collect_all_input_object_kind_for_reading(&self) -> IotaResult<Vec<InputObjectKind>>;
2009
2010    /// Splits the provided input objects into groups:
2011    /// 1. Input objects required by the transaction itself; may contain duplicates if an IOTA coin
2012    ///    is used both as an input and a gas coin.
2013    /// 2. A list of input objects required by each `MoveAuthenticator`( including the object to
2014    ///    authenticate) + the object to authenticate.
2015    fn split_input_objects_into_groups_for_reading(
2016        &self,
2017        input_objects: InputObjects,
2018    ) -> IotaResult<(InputObjects, Vec<(InputObjects, ObjectReadResult)>)>;
2019
2020    /// Checks if [`SenderSignedTransaction`] contains at least one shared
2021    /// object. This function checks shared objects from the
2022    /// `MoveAuthenticator`s if any.
2023    fn contains_shared_object(&self) -> bool;
2024
2025    /// Returns an iterator over all shared input objects related to this
2026    /// transaction, including those from `MoveAuthenticator`s if any.
2027    ///
2028    /// If a shared object appears with the same version but different
2029    /// mutability, only one instance which is mutable is returned.
2030    ///
2031    /// Panics if there are shared objects with the same ID but different
2032    /// initial versions.
2033    fn shared_input_objects(&self) -> Vec<SharedObjectReference>;
2034
2035    /// Returns an iterator over all input objects related to this
2036    /// transaction, including those from the `MoveAuthenticator`s if any.
2037    ///
2038    /// If an IOTA coin is used both as an input and as a gas coin, it will
2039    /// appear two times in the returned iterator.
2040    ///
2041    /// If a shared object appears both in the transaction and authenticator
2042    /// with different mutability, only one instance which is mutable is
2043    /// returned.
2044    ///
2045    /// Shared objects with the same ID but different versions are not allowed.
2046    fn input_objects(&self) -> IotaResult<Vec<InputObjectKind>>;
2047
2048    /// Checks if [`SenderSignedTransaction`] contains the `Random` object as an
2049    /// input.
2050    /// This function checks shared objects from the `MoveAuthenticator`s if
2051    /// any.
2052    fn uses_randomness(&self) -> bool;
2053}
2054
2055impl SenderSignedTransactionAPI for SenderSignedTransaction {
2056    fn new_from_sender_signature(
2057        tx: Transaction,
2058        tx_signature: SimpleSignature,
2059    ) -> SenderSignedTransaction {
2060        Self::new(tx, vec![tx_signature.into()])
2061    }
2062
2063    fn add_signature(&mut self, new_signature: SimpleSignature) {
2064        self.signed_transaction_mut()
2065            .signatures
2066            .push(new_signature.into());
2067    }
2068
2069    fn get_signer_sig_mapping(&self) -> IotaResult<BTreeMap<Address, &UserSignature>> {
2070        let mut mapping = BTreeMap::new();
2071        for sig in &self.signed_transaction().signatures {
2072            let address = sig.derive_address();
2073            mapping.insert(address, sig);
2074        }
2075        Ok(mapping)
2076    }
2077
2078    fn has_multisig(&self) -> bool {
2079        self.signatures().iter().any(|sig| sig.is_multisig())
2080    }
2081
2082    fn transaction_mut_for_testing(&mut self) -> &mut Transaction {
2083        &mut self.signed_transaction_mut().transaction
2084    }
2085
2086    fn tx_signatures_mut_for_testing(&mut self) -> &mut Vec<UserSignature> {
2087        &mut self.signed_transaction_mut().signatures
2088    }
2089
2090    fn serialized_size(&self) -> IotaResult<usize> {
2091        tx_bcs_size(self)
2092    }
2093
2094    fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> Result<usize, IotaError> {
2095        // Check that the features used by the user signatures are enabled on the
2096        // network.
2097        check_user_signature_protocol_compatibility(self, context.config)?;
2098
2099        // CRITICAL!!
2100        // Users cannot send system transactions.
2101        let tx = self.transaction();
2102        fp_ensure!(
2103            !tx.is_system_tx(),
2104            IotaError::UserInput {
2105                error: UserInputError::Unsupported(
2106                    "SenderSignedTransaction must not contain system transaction".to_string()
2107                )
2108            }
2109        );
2110
2111        // Checks to see if the transaction has expired
2112        if match &tx.expiration() {
2113            TransactionExpiration::None => false,
2114            TransactionExpiration::Epoch(exp_poch) => *exp_poch < context.epoch,
2115            _ => unimplemented!(
2116                "a new TransactionExpiration enum variant was added and needs to be handled"
2117            ),
2118        } {
2119            return Err(IotaError::TransactionExpired);
2120        }
2121
2122        // Enforce overall transaction size limit.
2123        let tx_size = check_transaction_size(self, context.config)?;
2124
2125        tx.validity_check(context.config)
2126            .map_err(Into::<IotaError>::into)?;
2127
2128        move_authenticators_validity_check(self, context.config)?;
2129
2130        Ok(tx_size)
2131    }
2132
2133    fn collect_all_input_object_kind_for_reading(&self) -> IotaResult<Vec<InputObjectKind>> {
2134        let mut input_objects_set = self
2135            .transaction()
2136            .input_objects()?
2137            .into_iter()
2138            .collect::<HashSet<_>>();
2139
2140        self.move_authenticators()
2141            .into_iter()
2142            .for_each(|authenticator| {
2143                input_objects_set.extend(authenticator.input_objects());
2144            });
2145
2146        Ok(input_objects_set.into_iter().collect::<Vec<_>>())
2147    }
2148
2149    fn split_input_objects_into_groups_for_reading(
2150        &self,
2151        input_objects: InputObjects,
2152    ) -> IotaResult<(InputObjects, Vec<(InputObjects, ObjectReadResult)>)> {
2153        let input_objects_map = input_objects
2154            .iter()
2155            .map(|o| (&o.input_object_kind, o))
2156            .collect::<HashMap<_, _>>();
2157
2158        let mut tx_input_objects: InputObjects = self
2159            .transaction()
2160            .input_objects()?
2161            .iter()
2162            .map(|k| {
2163                input_objects_map
2164                    .get(k)
2165                    .map(|&r| r.clone())
2166                    .expect("All transaction input objects are expected to be present")
2167            })
2168            .collect::<Vec<_>>()
2169            .into();
2170        if let Some((gas_object_id, version)) = input_objects.gas_object_cancellation() {
2171            tx_input_objects.set_gas_object_cancellation(gas_object_id, version);
2172        }
2173
2174        let per_authenticator_inputs =
2175            self.move_authenticators()
2176                .into_iter()
2177                .map(|move_authenticator| {
2178                    let authenticator_input_objects = move_authenticator
2179                        .input_objects()
2180                        .iter()
2181                        .map(|k| {
2182                            input_objects_map.get(k).map(|&r| r.clone()).expect(
2183                                "All authenticator input objects are expected to be present",
2184                            )
2185                        })
2186                        .collect::<Vec<_>>()
2187                        .into();
2188
2189                    let account_objects = move_authenticator
2190                        .object_to_authenticate()
2191                        .input_object_kind()
2192                        .iter()
2193                        .map(|k| {
2194                            input_objects_map
2195                                .get(k)
2196                                .map(|&r| r.clone())
2197                                .expect("Account object is expected to be present")
2198                        })
2199                        .collect::<Vec<_>>();
2200
2201                    debug_assert!(
2202                        account_objects.len() == 1,
2203                        "Only one account object must be loaded"
2204                    );
2205
2206                    (
2207                        authenticator_input_objects,
2208                        account_objects
2209                            .into_iter()
2210                            .next()
2211                            .expect("Account object is expected to be present"),
2212                    )
2213                })
2214                .collect();
2215
2216        Ok((tx_input_objects, per_authenticator_inputs))
2217    }
2218
2219    fn contains_shared_object(&self) -> bool {
2220        !self.shared_input_objects().is_empty()
2221    }
2222
2223    fn shared_input_objects(&self) -> Vec<SharedObjectReference> {
2224        // Vector is used to preserve the order of input objects.
2225        let mut input_objects = self.transaction().shared_input_objects();
2226
2227        // Add Move authenticator shared objects if any.
2228        self.move_authenticators()
2229            .into_iter()
2230            .for_each(|move_authenticator| {
2231                for auth_shared_object in move_authenticator.shared_objects() {
2232                    let entry = input_objects
2233                        .iter_mut()
2234                        .find(|o| o.object_id == auth_shared_object.object_id);
2235
2236                    match entry {
2237                        None => input_objects.push(auth_shared_object),
2238                        Some(existing) => {
2239                            left_union_shared_input_objects(existing, &auth_shared_object)
2240                                .expect("union of shared objects should not fail")
2241                        }
2242                    }
2243                }
2244            });
2245
2246        input_objects
2247    }
2248
2249    fn input_objects(&self) -> IotaResult<Vec<InputObjectKind>> {
2250        // Can contain duplicates in case of using the same IOTA coin as an input and as
2251        // a gas coin.
2252        let mut input_objects = self.transaction().input_objects()?;
2253
2254        // Add the `MoveAuthenticator` shared objects if any.
2255        merge_authenticator_input_objects(self.move_authenticators(), &mut input_objects)?;
2256
2257        Ok(input_objects)
2258    }
2259
2260    fn uses_randomness(&self) -> bool {
2261        self.shared_input_objects()
2262            .iter()
2263            .any(|obj| obj.object_id == ObjectId::RANDOMNESS_STATE)
2264    }
2265}
2266
2267fn check_user_signature_protocol_compatibility(
2268    signed_tx: &SenderSignedTransaction,
2269    config: &ProtocolConfig,
2270) -> IotaResult {
2271    for sig in signed_tx.signatures() {
2272        match sig {
2273            UserSignature::PasskeyAuthenticator(_) => {
2274                if !config.passkey_auth() {
2275                    return Err(IotaError::UserInput {
2276                        error: UserInputError::Unsupported(
2277                            "passkey is not enabled on this network".to_string(),
2278                        ),
2279                    });
2280                }
2281            }
2282            UserSignature::MoveAuthenticator(_) => {
2283                if !config.enable_move_authentication() {
2284                    return Err(IotaError::UserInput {
2285                        error: UserInputError::Unsupported(
2286                            "`Move authentication` is not enabled on this network".to_string(),
2287                        ),
2288                    });
2289                }
2290            }
2291            UserSignature::Simple(_) | UserSignature::Multisig(_) => (),
2292            _ => {
2293                unimplemented!("a new UserSignature variant was added and needs to be handled")
2294            }
2295        }
2296    }
2297
2298    Ok(())
2299}
2300
2301fn move_authenticators_validity_check(
2302    signed_tx: &SenderSignedTransaction,
2303    config: &ProtocolConfig,
2304) -> IotaResult {
2305    let authenticators = signed_tx.move_authenticators();
2306
2307    // Check each `MoveAuthenticator` validity.
2308    authenticators
2309        .iter()
2310        .try_for_each(|authenticator| authenticator.validity_check(config))?;
2311
2312    // Additional checks when `MoveAuthenticators` are present.
2313    let authenticators_num = authenticators.len();
2314    if authenticators_num > 0 {
2315        let tx = signed_tx.transaction();
2316
2317        fp_ensure!(
2318            tx.kind().is_programmable(),
2319            UserInputError::Unsupported(
2320                "SenderSignedTransaction with MoveAuthenticator must be a programmable transaction"
2321                    .to_string(),
2322            )
2323            .into()
2324        );
2325
2326        if !config.enable_move_authentication_for_sponsor() {
2327            fp_ensure!(
2328                authenticators_num == 1,
2329                UserInputError::Unsupported(
2330                    "SenderSignedTransaction with more than one MoveAuthenticator is not supported"
2331                        .to_string(),
2332                )
2333                .into()
2334            );
2335
2336            fp_ensure!(
2337                signed_tx.sender_move_authenticator().is_some(),
2338                UserInputError::Unsupported(
2339                    "SenderSignedTransaction can have MoveAuthenticator only for the sender"
2340                        .to_string(),
2341                )
2342                .into()
2343            );
2344        }
2345
2346        check_move_authenticators_input_consistency(tx, &authenticators)?;
2347    }
2348
2349    Ok(())
2350}
2351
2352fn check_move_authenticators_input_consistency(
2353    tx: &Transaction,
2354    authenticators: &[&MoveAuthenticator],
2355) -> IotaResult {
2356    // Get the input objects from the transaction data kind to skip the gas coins.
2357    let mut checked_inputs = tx
2358        .kind()
2359        .input_objects()?
2360        .into_iter()
2361        .map(|o| (o.object_id(), o))
2362        .collect::<HashMap<_, _>>();
2363
2364    authenticators.iter().try_for_each(|authenticator| {
2365        authenticator
2366            .input_objects()
2367            .iter()
2368            .try_for_each(|auth_input_object| {
2369                match checked_inputs.get(&auth_input_object.object_id()) {
2370                    Some(existing) => {
2371                        auth_input_object.check_consistency_for_authentication(existing)?
2372                    }
2373                    None => {
2374                        checked_inputs.insert(auth_input_object.object_id(), *auth_input_object);
2375                    }
2376                };
2377
2378                Ok(())
2379            })
2380    })
2381}
2382
2383impl Message for SenderSignedTransaction {
2384    type DigestType = TransactionDigest;
2385    const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
2386
2387    /// Computes the tx digest that encodes the Rust type prefix from Signable
2388    /// trait.
2389    fn digest(&self) -> Self::DigestType {
2390        self.transaction().digest()
2391    }
2392}
2393
2394impl<S> Envelope<SenderSignedTransaction, S> {
2395    pub fn sender_address(&self) -> Address {
2396        self.data().transaction().sender()
2397    }
2398
2399    pub fn gas(&self) -> &[ObjectReference] {
2400        self.data().transaction().gas()
2401    }
2402
2403    // Returns the primary key for this transaction.
2404    pub fn key(&self) -> TransactionKey {
2405        match &self.data().transaction().kind() {
2406            TransactionKind::RandomnessStateUpdate(rsu) => {
2407                TransactionKey::RandomnessRound(rsu.epoch, rsu.randomness_round)
2408            }
2409            _ => TransactionKey::Digest(*self.digest()),
2410        }
2411    }
2412
2413    // Returns non-Digest keys that could be used to refer to this transaction.
2414    //
2415    // At the moment this returns a single Option for efficiency, but if more key
2416    // types are added, the return type could change to Vec<TransactionKey>.
2417    pub fn non_digest_key(&self) -> Option<TransactionKey> {
2418        match &self.data().transaction().kind() {
2419            TransactionKind::RandomnessStateUpdate(rsu) => Some(TransactionKey::RandomnessRound(
2420                rsu.epoch,
2421                rsu.randomness_round,
2422            )),
2423            _ => None,
2424        }
2425    }
2426
2427    pub fn is_system_tx(&self) -> bool {
2428        self.data().transaction().is_system_tx()
2429    }
2430
2431    pub fn is_sponsored_tx(&self) -> bool {
2432        self.data().transaction().is_sponsored_tx()
2433    }
2434}
2435
2436impl TransactionEnvelope {
2437    pub fn from_data_and_signer(
2438        tx: Transaction,
2439        signers: Vec<&impl iota_sdk_crypto::Signer<SimpleSignature>>,
2440    ) -> Self {
2441        let signatures = {
2442            let digest = tx.signing_digest();
2443            signers.into_iter().map(|s| s.sign(&digest)).collect()
2444        };
2445        Self::from_data(tx, signatures)
2446    }
2447
2448    // TODO: Rename this function and above to make it clearer.
2449    pub fn from_data(tx: Transaction, signatures: Vec<SimpleSignature>) -> Self {
2450        Self::from_user_sig_data(tx, signatures.into_iter().map(|s| s.into()).collect())
2451    }
2452
2453    pub fn signature_from_signer(
2454        tx: Transaction,
2455        intent: Intent,
2456        signer: &impl iota_sdk_crypto::Signer<SimpleSignature>,
2457    ) -> SimpleSignature {
2458        let digest = IntentMessage::new(intent, tx).signing_digest();
2459        signer.sign(&digest)
2460    }
2461
2462    pub fn from_user_sig_data(tx: Transaction, signatures: Vec<UserSignature>) -> Self {
2463        Self::new(SenderSignedTransaction::new(tx, signatures))
2464    }
2465
2466    /// Returns the Base64 encoded tx_bytes
2467    /// and a list of Base64 encoded [`UserSignature`].
2468    pub fn to_tx_bytes_and_signatures(&self) -> (Base64, Vec<Base64>) {
2469        (
2470            Base64::from_bytes(&self.data().transaction().to_bcs()),
2471            self.data()
2472                .signatures()
2473                .iter()
2474                .map(|s| Base64::from_bytes(&s.to_bytes()))
2475                .collect(),
2476        )
2477    }
2478}
2479
2480impl VerifiedTransaction {
2481    pub fn new_genesis_transaction(objects: Vec<GenesisObject>, events: Vec<Event>) -> Self {
2482        GenesisTransaction { objects, events }
2483            .pipe(TransactionKind::Genesis)
2484            .pipe(Self::new_system_transaction)
2485    }
2486
2487    pub fn new_consensus_commit_prologue_v1(
2488        epoch: u64,
2489        round: u64,
2490        commit_timestamp_ms: CheckpointTimestamp,
2491        consensus_commit_digest: ConsensusCommitDigest,
2492        canceled_transactions: Vec<CanceledTransaction>,
2493    ) -> Self {
2494        ConsensusCommitPrologueV1 {
2495            epoch,
2496            round,
2497            // sub_dag_index is reserved for when we have multi commits per round.
2498            sub_dag_index: None,
2499            commit_timestamp_ms,
2500            consensus_commit_digest,
2501            consensus_determined_version_assignments:
2502                ConsensusDeterminedVersionAssignments::CanceledTransactions {
2503                    canceled_transactions,
2504                },
2505        }
2506        .pipe(TransactionKind::ConsensusCommitPrologueV1)
2507        .pipe(Self::new_system_transaction)
2508    }
2509
2510    pub fn new_randomness_state_update(
2511        epoch: u64,
2512        randomness_round: RandomnessRound,
2513        random_bytes: Vec<u8>,
2514        randomness_obj_initial_shared_version: Version,
2515    ) -> Self {
2516        RandomnessStateUpdate {
2517            epoch,
2518            randomness_round,
2519            random_bytes,
2520            randomness_obj_initial_shared_version,
2521        }
2522        .pipe(TransactionKind::RandomnessStateUpdate)
2523        .pipe(Self::new_system_transaction)
2524    }
2525
2526    pub fn new_end_of_epoch_transaction(txns: Vec<EndOfEpochTransactionKind>) -> Self {
2527        TransactionKind::EndOfEpoch(txns).pipe(Self::new_system_transaction)
2528    }
2529
2530    pub fn new_transaction_deny_rules_update(update: TransactionDenyRulesUpdate) -> Self {
2531        TransactionKind::TransactionDenyRulesUpdate(update).pipe(Self::new_system_transaction)
2532    }
2533
2534    fn new_system_transaction(system_transaction: TransactionKind) -> Self {
2535        system_transaction
2536            .pipe(Transaction::new_system_transaction)
2537            .pipe(|data| {
2538                SenderSignedTransaction::new_from_sender_signature(data, zero_ed25519_signature())
2539            })
2540            .pipe(TransactionEnvelope::new)
2541            .pipe(Self::new_from_verified)
2542    }
2543}
2544
2545impl VerifiedSignedTransaction {
2546    /// Use signing key to create a signed object.
2547    #[instrument(level = "trace", skip_all)]
2548    pub fn new(
2549        epoch: EpochId,
2550        transaction: VerifiedTransaction,
2551        authority: AuthorityName,
2552        secret: &dyn Signer<AuthoritySignature>,
2553    ) -> Self {
2554        Self::new_from_verified(SignedTransaction::new(
2555            epoch,
2556            transaction.into_inner().into_data(),
2557            secret,
2558            authority,
2559        ))
2560    }
2561}
2562
2563/// A transaction that is signed by a sender but not yet by an authority.
2564pub type TransactionEnvelope = Envelope<SenderSignedTransaction, EmptySignInfo>;
2565pub type VerifiedTransaction = VerifiedEnvelope<SenderSignedTransaction, EmptySignInfo>;
2566pub type TrustedTransaction = TrustedEnvelope<SenderSignedTransaction, EmptySignInfo>;
2567
2568/// A transaction that is signed by a sender and also by an authority.
2569pub type SignedTransaction = Envelope<SenderSignedTransaction, AuthoritySignInfo>;
2570pub type VerifiedSignedTransaction = VerifiedEnvelope<SenderSignedTransaction, AuthoritySignInfo>;
2571
2572impl TransactionEnvelope {
2573    pub fn verify_signature_for_testing(&self, verify_params: &VerifyParams) -> IotaResult {
2574        verify_sender_signed_data_message_signatures(self.data(), verify_params)
2575    }
2576
2577    pub fn try_into_verified_for_testing(
2578        self,
2579        verify_params: &VerifyParams,
2580    ) -> IotaResult<VerifiedTransaction> {
2581        self.verify_signature_for_testing(verify_params)?;
2582        Ok(VerifiedTransaction::new_from_verified(self))
2583    }
2584
2585    pub fn gas_price(&self) -> u64 {
2586        self.data().transaction().gas_price()
2587    }
2588}
2589
2590impl SignedTransaction {
2591    pub fn verify_signatures_authenticated_for_testing(
2592        &self,
2593        committee: &Committee,
2594        verify_params: &VerifyParams,
2595    ) -> IotaResult {
2596        verify_sender_signed_data_message_signatures(self.data(), verify_params)?;
2597
2598        self.auth_sig().verify_secure(
2599            self.data(),
2600            Intent::iota_app(IntentScope::SenderSignedTransaction),
2601            committee,
2602        )
2603    }
2604
2605    pub fn try_into_verified_for_testing(
2606        self,
2607        committee: &Committee,
2608        verify_params: &VerifyParams,
2609    ) -> IotaResult<VerifiedSignedTransaction> {
2610        self.verify_signatures_authenticated_for_testing(committee, verify_params)?;
2611        Ok(VerifiedSignedTransaction::new_from_verified(self))
2612    }
2613}
2614
2615pub type CertifiedTransaction = Envelope<SenderSignedTransaction, AuthorityStrongQuorumSignInfo>;
2616
2617impl CertifiedTransaction {
2618    pub fn certificate_digest(&self) -> CertificateDigest {
2619        let mut digest = DefaultHash::default();
2620        bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
2621        let hash = digest.finalize();
2622        CertificateDigest::new(hash.into())
2623    }
2624
2625    pub fn gas_price(&self) -> u64 {
2626        self.data().transaction().gas_price()
2627    }
2628
2629    // TODO: Eventually we should remove all calls to verify_signature
2630    // and make sure they all call verify to avoid repeated verifications.
2631    #[instrument(level = "trace", skip_all)]
2632    pub fn verify_signatures_authenticated(
2633        &self,
2634        committee: &Committee,
2635        verify_params: &VerifyParams,
2636    ) -> IotaResult {
2637        verify_sender_signed_data_message_signatures(self.data(), verify_params)?;
2638        self.verify_committee_sigs_only(committee)
2639    }
2640
2641    pub fn try_into_verified_for_testing(
2642        self,
2643        committee: &Committee,
2644        verify_params: &VerifyParams,
2645    ) -> IotaResult<VerifiedCertificate> {
2646        self.verify_signatures_authenticated(committee, verify_params)?;
2647        Ok(VerifiedCertificate::new_from_verified(self))
2648    }
2649
2650    pub fn verify_committee_sigs_only(&self, committee: &Committee) -> IotaResult {
2651        self.auth_sig().verify_secure(
2652            self.data(),
2653            Intent::iota_app(IntentScope::SenderSignedTransaction),
2654            committee,
2655        )
2656    }
2657}
2658
2659pub type VerifiedCertificate =
2660    VerifiedEnvelope<SenderSignedTransaction, AuthorityStrongQuorumSignInfo>;
2661pub type TrustedCertificate =
2662    TrustedEnvelope<SenderSignedTransaction, AuthorityStrongQuorumSignInfo>;
2663
2664#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
2665pub enum InputObjectKind {
2666    // A Move package, must be immutable.
2667    MovePackage(ObjectId),
2668    // A Move object, either immutable, or owned mutable.
2669    ImmOrOwnedMoveObject(ObjectReference),
2670    // A Move object that's shared and mutable.
2671    SharedMoveObject {
2672        id: ObjectId,
2673        initial_shared_version: Version,
2674        mutable: bool,
2675    },
2676}
2677
2678impl InputObjectKind {
2679    pub fn object_id(&self) -> ObjectId {
2680        match self {
2681            Self::MovePackage(id) => *id,
2682            Self::ImmOrOwnedMoveObject(object_ref) => object_ref.object_id,
2683            Self::SharedMoveObject { id, .. } => *id,
2684        }
2685    }
2686
2687    pub fn version(&self) -> Option<Version> {
2688        match self {
2689            Self::MovePackage(..) => None,
2690            Self::ImmOrOwnedMoveObject(object_ref) => Some(object_ref.version),
2691            Self::SharedMoveObject { .. } => None,
2692        }
2693    }
2694
2695    pub fn object_not_found_error(&self) -> UserInputError {
2696        match *self {
2697            Self::MovePackage(package_id) => {
2698                UserInputError::DependentPackageNotFound { package_id }
2699            }
2700            Self::ImmOrOwnedMoveObject(object_ref) => UserInputError::ObjectNotFound {
2701                object_id: object_ref.object_id,
2702                version: Some(object_ref.version),
2703            },
2704            Self::SharedMoveObject { id, .. } => UserInputError::ObjectNotFound {
2705                object_id: id,
2706                version: None,
2707            },
2708        }
2709    }
2710
2711    pub fn is_shared_object(&self) -> bool {
2712        matches!(self, Self::SharedMoveObject { .. })
2713    }
2714
2715    pub fn is_mutable(&self) -> bool {
2716        match self {
2717            Self::MovePackage(..) => false,
2718            Self::ImmOrOwnedMoveObject(_) => true,
2719            Self::SharedMoveObject { mutable, .. } => *mutable,
2720        }
2721    }
2722
2723    /// Merges another InputObjectKind into self.
2724    ///
2725    /// For shared objects, if either is mutable, the result is mutable. Fails
2726    /// if the IDs or initial versions do not match.
2727    /// For non-shared objects, fails if they are not equal.
2728    pub fn left_union_with_checks(&mut self, other: &InputObjectKind) -> UserInputResult<()> {
2729        match self {
2730            InputObjectKind::MovePackage(_) | InputObjectKind::ImmOrOwnedMoveObject(_) => {
2731                fp_ensure!(
2732                    self == other,
2733                    UserInputError::InconsistentInput {
2734                        object_id: other.object_id(),
2735                    }
2736                );
2737            }
2738            InputObjectKind::SharedMoveObject {
2739                id,
2740                initial_shared_version,
2741                mutable,
2742            } => match other {
2743                InputObjectKind::MovePackage(_) | InputObjectKind::ImmOrOwnedMoveObject(_) => {
2744                    fp_bail!(UserInputError::NotSharedObject)
2745                }
2746                InputObjectKind::SharedMoveObject {
2747                    id: other_id,
2748                    initial_shared_version: other_initial_shared_version,
2749                    mutable: other_mutable,
2750                } => {
2751                    fp_ensure!(id == other_id, UserInputError::SharedObjectIdMismatch);
2752                    fp_ensure!(
2753                        initial_shared_version == other_initial_shared_version,
2754                        UserInputError::SharedObjectStartingVersionMismatch
2755                    );
2756
2757                    if !*mutable && *other_mutable {
2758                        *mutable = *other_mutable;
2759                    }
2760                }
2761            },
2762        }
2763
2764        Ok(())
2765    }
2766
2767    /// Checks that `self` and `other` are equal for non-shared objects.
2768    /// For shared objects, checks that IDs and initial versions match while
2769    /// mutability can be different.
2770    pub fn check_consistency_for_authentication(
2771        &self,
2772        other: &InputObjectKind,
2773    ) -> UserInputResult<()> {
2774        match self {
2775            InputObjectKind::MovePackage(_) | InputObjectKind::ImmOrOwnedMoveObject(_) => {
2776                fp_ensure!(
2777                    self == other,
2778                    UserInputError::InconsistentInput {
2779                        object_id: self.object_id()
2780                    }
2781                );
2782            }
2783            InputObjectKind::SharedMoveObject {
2784                id,
2785                initial_shared_version,
2786                mutable: _,
2787            } => match other {
2788                InputObjectKind::MovePackage(_) | InputObjectKind::ImmOrOwnedMoveObject(_) => {
2789                    fp_bail!(UserInputError::InconsistentInput {
2790                        object_id: self.object_id()
2791                    })
2792                }
2793                InputObjectKind::SharedMoveObject {
2794                    id: other_id,
2795                    initial_shared_version: other_initial_shared_version,
2796                    mutable: _,
2797                } => {
2798                    fp_ensure!(
2799                        id == other_id,
2800                        UserInputError::InconsistentInput { object_id: *id }
2801                    );
2802                    fp_ensure!(
2803                        initial_shared_version == other_initial_shared_version,
2804                        UserInputError::InconsistentInput { object_id: *id }
2805                    );
2806                }
2807            },
2808        }
2809
2810        Ok(())
2811    }
2812}
2813
2814/// The result of reading an object for execution. Because shared objects may be
2815/// deleted, one possible result of reading a shared object is that
2816/// ObjectReadResultKind::Deleted is returned.
2817#[derive(Clone, Debug)]
2818pub struct ObjectReadResult {
2819    pub input_object_kind: InputObjectKind,
2820    pub object: ObjectReadResultKind,
2821}
2822
2823#[derive(Clone, PartialEq)]
2824pub enum ObjectReadResultKind {
2825    Object(Object),
2826    // The version of the object that the transaction intended to read, and the digest of the tx
2827    // that deleted it.
2828    DeletedSharedObject(Version, TransactionDigest),
2829    // A shared object in a cancelled transaction. The sequence number embeds cancellation reason.
2830    CancelledTransactionObject(Version),
2831}
2832
2833impl std::fmt::Debug for ObjectReadResultKind {
2834    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2835        match self {
2836            ObjectReadResultKind::Object(obj) => {
2837                write!(f, "Object({:?})", obj.object_ref())
2838            }
2839            ObjectReadResultKind::DeletedSharedObject(seq, digest) => {
2840                write!(f, "DeletedSharedObject({seq}, {digest})")
2841            }
2842            ObjectReadResultKind::CancelledTransactionObject(seq) => {
2843                write!(f, "CancelledTransactionObject({seq})")
2844            }
2845        }
2846    }
2847}
2848
2849impl From<Object> for ObjectReadResultKind {
2850    fn from(object: Object) -> Self {
2851        Self::Object(object)
2852    }
2853}
2854
2855impl ObjectReadResult {
2856    pub fn new(input_object_kind: InputObjectKind, object: ObjectReadResultKind) -> Self {
2857        if let (
2858            InputObjectKind::ImmOrOwnedMoveObject(_),
2859            ObjectReadResultKind::DeletedSharedObject(_, _),
2860        ) = (&input_object_kind, &object)
2861        {
2862            panic!("only shared objects can be DeletedSharedObject");
2863        }
2864
2865        if let (
2866            InputObjectKind::ImmOrOwnedMoveObject(_),
2867            ObjectReadResultKind::CancelledTransactionObject(_),
2868        ) = (&input_object_kind, &object)
2869        {
2870            panic!("only shared objects can be CancelledTransactionObject");
2871        }
2872
2873        Self {
2874            input_object_kind,
2875            object,
2876        }
2877    }
2878
2879    pub fn id(&self) -> ObjectId {
2880        self.input_object_kind.object_id()
2881    }
2882
2883    pub fn as_object(&self) -> Option<&Object> {
2884        match &self.object {
2885            ObjectReadResultKind::Object(object) => Some(object),
2886            ObjectReadResultKind::DeletedSharedObject(_, _) => None,
2887            ObjectReadResultKind::CancelledTransactionObject(_) => None,
2888        }
2889    }
2890
2891    pub fn new_from_gas_object(gas: &Object) -> Self {
2892        let objref = gas.object_ref();
2893        Self {
2894            input_object_kind: InputObjectKind::ImmOrOwnedMoveObject(objref),
2895            object: ObjectReadResultKind::Object(gas.clone()),
2896        }
2897    }
2898
2899    pub fn is_mutable(&self) -> bool {
2900        match (&self.input_object_kind, &self.object) {
2901            (InputObjectKind::MovePackage(_), _) => false,
2902            (InputObjectKind::ImmOrOwnedMoveObject(_), ObjectReadResultKind::Object(object)) => {
2903                !object.is_immutable()
2904            }
2905            (
2906                InputObjectKind::ImmOrOwnedMoveObject(_),
2907                ObjectReadResultKind::DeletedSharedObject(_, _),
2908            ) => unreachable!(),
2909            (
2910                InputObjectKind::ImmOrOwnedMoveObject(_),
2911                ObjectReadResultKind::CancelledTransactionObject(_),
2912            ) => unreachable!(),
2913            (InputObjectKind::SharedMoveObject { mutable, .. }, _) => *mutable,
2914        }
2915    }
2916
2917    pub fn is_shared_object(&self) -> bool {
2918        self.input_object_kind.is_shared_object()
2919    }
2920
2921    pub fn is_deleted_shared_object(&self) -> bool {
2922        self.deletion_info().is_some()
2923    }
2924
2925    pub fn deletion_info(&self) -> Option<(Version, TransactionDigest)> {
2926        match &self.object {
2927            ObjectReadResultKind::DeletedSharedObject(v, tx) => Some((*v, *tx)),
2928            _ => None,
2929        }
2930    }
2931
2932    /// Return the object ref iff the object is an owned object (i.e. not
2933    /// shared, not immutable).
2934    pub fn get_owned_objref(&self) -> Option<ObjectReference> {
2935        match (&self.input_object_kind, &self.object) {
2936            (InputObjectKind::MovePackage(_), _) => None,
2937            (
2938                InputObjectKind::ImmOrOwnedMoveObject(objref),
2939                ObjectReadResultKind::Object(object),
2940            ) => {
2941                if object.is_immutable() {
2942                    None
2943                } else {
2944                    Some(*objref)
2945                }
2946            }
2947            (
2948                InputObjectKind::ImmOrOwnedMoveObject(_),
2949                ObjectReadResultKind::DeletedSharedObject(_, _),
2950            ) => unreachable!(),
2951            (
2952                InputObjectKind::ImmOrOwnedMoveObject(_),
2953                ObjectReadResultKind::CancelledTransactionObject(_),
2954            ) => unreachable!(),
2955            (InputObjectKind::SharedMoveObject { .. }, _) => None,
2956        }
2957    }
2958
2959    pub fn is_owned(&self) -> bool {
2960        self.get_owned_objref().is_some()
2961    }
2962
2963    pub fn to_shared_input(&self) -> Option<SharedInput> {
2964        match self.input_object_kind {
2965            InputObjectKind::MovePackage(_) => None,
2966            InputObjectKind::ImmOrOwnedMoveObject(_) => None,
2967            InputObjectKind::SharedMoveObject { id, mutable, .. } => Some(match &self.object {
2968                ObjectReadResultKind::Object(obj) => SharedInput::Existing(obj.object_ref()),
2969                ObjectReadResultKind::DeletedSharedObject(seq, digest) => {
2970                    SharedInput::Deleted((id, *seq, mutable, *digest))
2971                }
2972                ObjectReadResultKind::CancelledTransactionObject(seq) => {
2973                    SharedInput::Cancelled((id, *seq))
2974                }
2975            }),
2976        }
2977    }
2978
2979    pub fn get_previous_transaction(&self) -> Option<TransactionDigest> {
2980        match &self.object {
2981            ObjectReadResultKind::Object(obj) => Some(obj.previous_transaction),
2982            ObjectReadResultKind::DeletedSharedObject(_, digest) => Some(*digest),
2983            ObjectReadResultKind::CancelledTransactionObject(_) => None,
2984        }
2985    }
2986}
2987
2988/// The input objects that carry a cancelled transaction's cancellation
2989/// version, and therefore the objects reported to the client as the cause.
2990#[derive(Clone, Debug, PartialEq, Eq)]
2991pub enum CancelledObjects {
2992    /// The transaction's shared inputs, reported as the congested objects.
2993    SharedObjects(Vec<ObjectId>),
2994    /// The gas object of a transaction without shared inputs. Consensus only
2995    /// assigns a cancellation version there when the execution workers are
2996    /// congested, so no individual object is responsible and none is
2997    /// reported.
2998    GasObject,
2999}
3000
3001#[derive(Clone)]
3002pub struct InputObjects {
3003    objects: Vec<ObjectReadResult>,
3004    /// Cancellation version assigned to the gas object when a transaction
3005    /// without shared inputs is cancelled by consensus (execution-worker
3006    /// congestion). Transactions with shared inputs carry cancellation
3007    /// versions on their shared object read results instead. Unlike those,
3008    /// the gas object itself is still read normally, because the cancelled
3009    /// execution must charge gas to it.
3010    gas_object_cancellation: Option<(ObjectId, Version)>,
3011}
3012
3013impl std::fmt::Debug for InputObjects {
3014    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3015        match &self.gas_object_cancellation {
3016            None => f.debug_list().entries(self.objects.iter()).finish(),
3017            Some(gas_object_cancellation) => f
3018                .debug_struct("InputObjects")
3019                .field("objects", &self.objects)
3020                .field("gas_object_cancellation", gas_object_cancellation)
3021                .finish(),
3022        }
3023    }
3024}
3025
3026// An InputObjects new-type that has been verified by iota-transaction-checks,
3027// and can be safely passed to execution.
3028pub struct CheckedInputObjects(InputObjects);
3029
3030// DO NOT CALL outside of iota-transaction-checks, genesis, or replay.
3031//
3032// CheckedInputObjects should really be defined in iota-transaction-checks so
3033// that we can make public construction impossible. But we can't do that because
3034// it would result in circular dependencies.
3035impl CheckedInputObjects {
3036    // Only called by iota-transaction-checks.
3037    pub fn new_with_checked_transaction_inputs(inputs: InputObjects) -> Self {
3038        Self(inputs)
3039    }
3040
3041    // Only called when building the genesis transaction
3042    pub fn new_for_genesis(input_objects: Vec<ObjectReadResult>) -> Self {
3043        Self(InputObjects::new(input_objects))
3044    }
3045
3046    // Only called from the replay tool.
3047    pub fn new_for_replay(input_objects: InputObjects) -> Self {
3048        Self(input_objects)
3049    }
3050
3051    pub fn inner(&self) -> &InputObjects {
3052        &self.0
3053    }
3054
3055    pub fn into_inner(self) -> InputObjects {
3056        self.0
3057    }
3058}
3059
3060impl From<Vec<ObjectReadResult>> for InputObjects {
3061    fn from(objects: Vec<ObjectReadResult>) -> Self {
3062        Self::new(objects)
3063    }
3064}
3065
3066impl InputObjects {
3067    pub fn new(objects: Vec<ObjectReadResult>) -> Self {
3068        Self {
3069            objects,
3070            gas_object_cancellation: None,
3071        }
3072    }
3073
3074    /// Marks this transaction as cancelled via the version assigned to its
3075    /// gas object. Only valid for transactions without shared inputs; a
3076    /// transaction with shared inputs carries cancellation versions on its
3077    /// shared object read results.
3078    pub fn set_gas_object_cancellation(&mut self, gas_object_id: ObjectId, version: Version) {
3079        assert!(
3080            version.is_canceled(),
3081            "gas object cancellation must use a cancellation version, got {version:?}"
3082        );
3083        assert!(
3084            !self.objects.iter().any(|obj| obj.is_shared_object()),
3085            "transactions with shared inputs carry cancellation on their shared objects"
3086        );
3087        self.gas_object_cancellation = Some((gas_object_id, version));
3088    }
3089
3090    pub fn gas_object_cancellation(&self) -> Option<(ObjectId, Version)> {
3091        self.gas_object_cancellation
3092    }
3093
3094    pub fn len(&self) -> usize {
3095        self.objects.len()
3096    }
3097
3098    pub fn is_empty(&self) -> bool {
3099        self.objects.is_empty()
3100    }
3101
3102    pub fn contains_deleted_objects(&self) -> bool {
3103        self.objects
3104            .iter()
3105            .any(|obj| obj.is_deleted_shared_object())
3106    }
3107
3108    // Returns the objects responsible for a transaction being cancelled, and the
3109    // corresponding reason for cancellation.
3110    pub fn get_cancelled_objects(&self) -> Option<(CancelledObjects, Version)> {
3111        if let Some((_, version)) = &self.gas_object_cancellation {
3112            return Some((CancelledObjects::GasObject, *version));
3113        }
3114
3115        let mut contains_cancelled = false;
3116        let mut cancel_reason = None;
3117        let mut cancelled_objects = Vec::new();
3118        for obj in &self.objects {
3119            if let ObjectReadResultKind::CancelledTransactionObject(version) = obj.object {
3120                contains_cancelled = true;
3121                if version.is_congested() || version == Version::RANDOMNESS_UNAVAILABLE {
3122                    // Verify we don't have multiple cancellation reasons.
3123                    assert!(cancel_reason.is_none() || cancel_reason == Some(version));
3124                    cancel_reason = Some(version);
3125                    cancelled_objects.push(obj.id());
3126                }
3127            }
3128        }
3129
3130        if !cancelled_objects.is_empty() {
3131            Some((
3132                CancelledObjects::SharedObjects(cancelled_objects),
3133                cancel_reason
3134                    .expect("there should be a cancel reason if there are cancelled objects"),
3135            ))
3136        } else {
3137            assert!(!contains_cancelled);
3138            None
3139        }
3140    }
3141
3142    pub fn filter_owned_objects(&self) -> Vec<ObjectReference> {
3143        let owned_objects: Vec<_> = self
3144            .objects
3145            .iter()
3146            .filter_map(|obj| obj.get_owned_objref())
3147            .collect();
3148
3149        trace!(
3150            num_mutable_objects = owned_objects.len(),
3151            "Checked locks and found mutable objects"
3152        );
3153
3154        owned_objects
3155    }
3156
3157    pub fn filter_shared_objects(&self) -> Vec<SharedInput> {
3158        self.objects
3159            .iter()
3160            .filter(|obj| obj.is_shared_object())
3161            .map(|obj| {
3162                obj.to_shared_input()
3163                    .expect("already filtered for shared objects")
3164            })
3165            .collect()
3166    }
3167
3168    pub fn transaction_dependencies(&self) -> BTreeSet<TransactionDigest> {
3169        self.objects
3170            .iter()
3171            .filter_map(|obj| obj.get_previous_transaction())
3172            .collect()
3173    }
3174
3175    pub fn mutable_inputs(&self) -> BTreeMap<ObjectId, (VersionDigest, Owner)> {
3176        self.objects
3177            .iter()
3178            .filter_map(
3179                |ObjectReadResult {
3180                     input_object_kind,
3181                     object,
3182                 }| match (input_object_kind, object) {
3183                    (InputObjectKind::MovePackage(_), _) => None,
3184                    (
3185                        InputObjectKind::ImmOrOwnedMoveObject(object_ref),
3186                        ObjectReadResultKind::Object(object),
3187                    ) => {
3188                        if object.is_immutable() {
3189                            None
3190                        } else {
3191                            Some((
3192                                object_ref.object_id,
3193                                ((object_ref.version, object_ref.digest), object.owner),
3194                            ))
3195                        }
3196                    }
3197                    (
3198                        InputObjectKind::ImmOrOwnedMoveObject(_),
3199                        ObjectReadResultKind::DeletedSharedObject(_, _),
3200                    ) => {
3201                        unreachable!()
3202                    }
3203                    (
3204                        InputObjectKind::SharedMoveObject { .. },
3205                        ObjectReadResultKind::DeletedSharedObject(_, _),
3206                    ) => None,
3207                    (
3208                        InputObjectKind::SharedMoveObject { mutable, .. },
3209                        ObjectReadResultKind::Object(object),
3210                    ) => {
3211                        if *mutable {
3212                            let oref = object.object_ref();
3213                            Some((oref.object_id, ((oref.version, oref.digest), object.owner)))
3214                        } else {
3215                            None
3216                        }
3217                    }
3218                    (
3219                        InputObjectKind::ImmOrOwnedMoveObject(_),
3220                        ObjectReadResultKind::CancelledTransactionObject(_),
3221                    ) => {
3222                        unreachable!()
3223                    }
3224                    (
3225                        InputObjectKind::SharedMoveObject { .. },
3226                        ObjectReadResultKind::CancelledTransactionObject(_),
3227                    ) => None,
3228                },
3229            )
3230            .collect()
3231    }
3232
3233    /// The version to set on objects created by the computation that `self` is
3234    /// input to. Guaranteed to be strictly greater than the versions of all
3235    /// input objects and objects received in the transaction.
3236    pub fn lamport_timestamp(&self, receiving_objects: &[ObjectReference]) -> Version {
3237        let input_versions = self
3238            .objects
3239            .iter()
3240            .filter_map(|object| match &object.object {
3241                ObjectReadResultKind::Object(object) => {
3242                    object.data.as_opt_struct().map(MoveStruct::version)
3243                }
3244                ObjectReadResultKind::DeletedSharedObject(v, _) => Some(*v),
3245                ObjectReadResultKind::CancelledTransactionObject(_) => None,
3246            })
3247            .chain(
3248                receiving_objects
3249                    .iter()
3250                    .map(|object_ref| object_ref.version),
3251            );
3252
3253        Version::lamport_increment(input_versions).unwrap()
3254    }
3255
3256    pub fn object_kinds(&self) -> impl Iterator<Item = &InputObjectKind> {
3257        self.objects.iter().map(
3258            |ObjectReadResult {
3259                 input_object_kind, ..
3260             }| input_object_kind,
3261        )
3262    }
3263
3264    pub fn into_object_map(self) -> BTreeMap<ObjectId, Object> {
3265        self.objects
3266            .into_iter()
3267            .filter_map(|o| o.as_object().map(|object| (o.id(), object.clone())))
3268            .collect()
3269    }
3270
3271    pub fn push(&mut self, object: ObjectReadResult) {
3272        self.objects.push(object);
3273    }
3274
3275    // If it contains then it returns the ObjectReadResult
3276    pub fn find_object_id_mut(&mut self, object_id: ObjectId) -> Option<&mut ObjectReadResult> {
3277        self.objects.iter_mut().find(|o| o.id() == object_id)
3278    }
3279
3280    pub fn iter(&self) -> impl Iterator<Item = &ObjectReadResult> {
3281        self.objects.iter()
3282    }
3283
3284    pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
3285        self.objects.iter().filter_map(|o| o.as_object())
3286    }
3287}
3288
3289// Result of attempting to read a receiving object (currently only at signing
3290// time). Because an object may have been previously received and deleted, the
3291// result may be ReceivingObjectReadResultKind::PreviouslyReceivedObject.
3292#[derive(Clone, Debug)]
3293pub enum ReceivingObjectReadResultKind {
3294    Object(Object),
3295    // The object was received by some other transaction, and we were not able to read it
3296    PreviouslyReceivedObject,
3297}
3298
3299impl ReceivingObjectReadResultKind {
3300    pub fn as_object(&self) -> Option<&Object> {
3301        match &self {
3302            Self::Object(object) => Some(object),
3303            Self::PreviouslyReceivedObject => None,
3304        }
3305    }
3306}
3307
3308pub struct ReceivingObjectReadResult {
3309    pub object_ref: ObjectReference,
3310    pub object: ReceivingObjectReadResultKind,
3311}
3312
3313impl ReceivingObjectReadResult {
3314    pub fn new(object_ref: ObjectReference, object: ReceivingObjectReadResultKind) -> Self {
3315        Self { object_ref, object }
3316    }
3317
3318    pub fn is_previously_received(&self) -> bool {
3319        matches!(
3320            self.object,
3321            ReceivingObjectReadResultKind::PreviouslyReceivedObject
3322        )
3323    }
3324}
3325
3326impl From<Object> for ReceivingObjectReadResultKind {
3327    fn from(object: Object) -> Self {
3328        Self::Object(object)
3329    }
3330}
3331
3332pub struct ReceivingObjects {
3333    pub objects: Vec<ReceivingObjectReadResult>,
3334}
3335
3336impl ReceivingObjects {
3337    pub fn iter(&self) -> impl Iterator<Item = &ReceivingObjectReadResult> {
3338        self.objects.iter()
3339    }
3340
3341    pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
3342        self.objects.iter().filter_map(|o| o.object.as_object())
3343    }
3344}
3345
3346impl From<Vec<ReceivingObjectReadResult>> for ReceivingObjects {
3347    fn from(objects: Vec<ReceivingObjectReadResult>) -> Self {
3348        Self { objects }
3349    }
3350}
3351
3352impl Display for CertifiedTransaction {
3353    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3354        let mut writer = String::new();
3355        writeln!(writer, "Transaction Hash: {:?}", self.digest())?;
3356        writeln!(
3357            writer,
3358            "Signed Authorities Bitmap : {:?}",
3359            self.auth_sig().signers_map
3360        )?;
3361        write!(writer, "{}", self.data().transaction().kind())?;
3362        write!(f, "{writer}")
3363    }
3364}
3365
3366/// TransactionKey uniquely identifies a transaction across all epochs.
3367/// Note that a single transaction may have multiple keys, for example a
3368/// RandomnessStateUpdate could be identified by both `Digest` and
3369/// `RandomnessRound`.
3370#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
3371pub enum TransactionKey {
3372    Digest(TransactionDigest),
3373    RandomnessRound(EpochId, RandomnessRound),
3374}
3375
3376impl TransactionKey {
3377    pub fn unwrap_digest(&self) -> &TransactionDigest {
3378        match self {
3379            TransactionKey::Digest(d) => d,
3380            _ => panic!("called expect_digest on a non-Digest TransactionKey: {self:?}"),
3381        }
3382    }
3383
3384    pub fn as_digest(&self) -> Option<&TransactionDigest> {
3385        match self {
3386            TransactionKey::Digest(d) => Some(d),
3387            _ => None,
3388        }
3389    }
3390}