iota_sdk/client/api/block_builder/input_selection/core/requirement/
alias.rs

1// Copyright 2023 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use super::{Error, InputSelection, Requirement};
5use crate::{
6    client::{api::input_selection::Burn, secret::types::InputSigningData},
7    types::block::output::{AliasId, AliasTransition, Output, OutputId},
8};
9
10pub(crate) fn is_alias_transition<'a>(
11    input: &Output,
12    input_id: OutputId,
13    outputs: &[Output],
14    burn: impl Into<Option<&'a Burn>>,
15) -> Option<AliasTransition> {
16    if let Output::Alias(alias_input) = &input {
17        let alias_id = alias_input.alias_id_non_null(&input_id);
18        // Checks if the alias exists in the outputs and gets the transition type.
19        for output in outputs.iter() {
20            if let Output::Alias(alias_output) = output {
21                if *alias_output.alias_id() == alias_id {
22                    if alias_output.state_index() == alias_input.state_index() {
23                        // Governance transition.
24                        return Some(AliasTransition::Governance);
25                    } else {
26                        // State transition.
27                        return Some(AliasTransition::State);
28                    }
29                }
30            }
31        }
32        if let Some(burn) = burn.into() {
33            if burn.aliases().contains(&alias_id) {
34                return Some(AliasTransition::Governance);
35            }
36        }
37    }
38    None
39}
40
41/// Checks if an output is an alias with a given non null alias ID.
42/// Calling it with a null alias ID may lead to undefined behavior.
43pub(crate) fn is_alias_with_id_non_null(output: &Output, alias_id: &AliasId) -> bool {
44    if let Output::Alias(alias) = output {
45        alias.alias_id() == alias_id
46    } else {
47        false
48    }
49}
50
51/// Checks if an output is an alias with output ID that matches the given alias ID.
52pub(crate) fn is_alias_with_id(output: &Output, output_id: &OutputId, alias_id: &AliasId) -> bool {
53    if let Output::Alias(alias) = output {
54        &alias.alias_id_non_null(output_id) == alias_id
55    } else {
56        false
57    }
58}
59
60impl InputSelection {
61    /// Fulfills an alias requirement by selecting the appropriate alias from the available inputs.
62    pub(crate) fn fulfill_alias_requirement(
63        &mut self,
64        alias_id: AliasId,
65        alias_transition: AliasTransition,
66    ) -> Result<Vec<(InputSigningData, Option<AliasTransition>)>, Error> {
67        // Check that the alias is not burned when a state transition is required.
68        if alias_transition.is_state()
69            && self
70                .burn
71                .as_ref()
72                .map_or(false, |burn| burn.aliases.contains(&alias_id))
73        {
74            return Err(Error::UnfulfillableRequirement(Requirement::Alias(
75                alias_id,
76                alias_transition,
77            )));
78        }
79
80        let selected_input = self
81            .selected_inputs
82            .iter()
83            .find(|input| is_alias_with_id(&input.output, input.output_id(), &alias_id));
84
85        // If a state transition is not required and the alias has already been selected, no additional check has to be
86        // performed.
87        if !alias_transition.is_state() && selected_input.is_some() {
88            log::debug!(
89                "{alias_id:?}/{alias_transition:?} requirement already fulfilled by {:?}",
90                selected_input.unwrap().output_id()
91            );
92            return Ok(Vec::new());
93        }
94
95        let available_index = self
96            .available_inputs
97            .iter()
98            .position(|input| is_alias_with_id(&input.output, input.output_id(), &alias_id));
99
100        // If the alias was not already selected and it not available, the requirement can't be fulfilled.
101        if selected_input.is_none() && available_index.is_none() {
102            return Err(Error::UnfulfillableRequirement(Requirement::Alias(
103                alias_id,
104                alias_transition,
105            )));
106        }
107
108        // If a state transition is not required, we can simply select the alias.
109        if !alias_transition.is_state() {
110            // Remove the output from the available inputs, swap to make it O(1).
111            let input = self.available_inputs.swap_remove(available_index.unwrap());
112
113            log::debug!(
114                "{alias_id:?}/{alias_transition:?} requirement fulfilled by {:?}",
115                input.output_id()
116            );
117
118            // PANIC: safe to unwrap as it's been checked that it can't be None when a state transition is not required.
119            return Ok(vec![(input, None)]);
120        }
121
122        // At this point, a state transition is required so we need to verify that an alias output describing a
123        // governance transition was not provided.
124
125        // PANIC: safe to unwrap as it's been checked that both can't be None at the same time.
126        let input = selected_input.unwrap_or_else(|| &self.available_inputs[available_index.unwrap()]);
127
128        if is_alias_transition(&input.output, *input.output_id(), &self.outputs, self.burn.as_ref())
129            == Some(AliasTransition::Governance)
130        {
131            return Err(Error::UnfulfillableRequirement(Requirement::Alias(
132                alias_id,
133                alias_transition,
134            )));
135        }
136
137        if let Some(available_index) = available_index {
138            // Remove the output from the available inputs, swap to make it O(1).
139            let input = self.available_inputs.swap_remove(available_index);
140
141            log::debug!(
142                "{alias_id:?}/{alias_transition:?} requirement fulfilled by {:?}",
143                input.output_id()
144            );
145
146            return Ok(vec![(input, None)]);
147        }
148
149        log::debug!(
150            "{alias_id:?}/{alias_transition:?} requirement already fulfilled by {:?}",
151            selected_input.unwrap().output_id()
152        );
153
154        Ok(Vec::new())
155    }
156}