1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// Copyright 2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
//! input selection for utxo chains
use crypto::keys::bip44::Bip44;
use crate::{
client::{
api::{block_builder::ClientBlockBuilder, search_address},
secret::types::InputSigningData,
Client, Result,
},
types::block::{
address::Address,
output::{Output, OutputWithMetadata},
},
};
/// Get recursively owned alias and nft outputs and add them to the utxo_chains
pub(crate) async fn get_alias_and_nft_outputs_recursively(
client: &Client,
utxo_chains: &mut Vec<(Address, OutputWithMetadata)>,
) -> Result<()> {
log::debug!("[get_alias_and_nft_outputs_recursively]");
let current_time = client.get_time_checked().await?;
let mut processed_alias_nft_addresses = std::collections::HashSet::new();
// Add addresses for alias and nft outputs we already have
for (_unlock_address, output_with_meta) in utxo_chains.iter() {
match output_with_meta.output() {
Output::Alias(alias_output) => {
processed_alias_nft_addresses.insert(Address::Alias(
alias_output.alias_address(output_with_meta.metadata().output_id()),
));
}
Output::Nft(nft_output) => {
processed_alias_nft_addresses.insert(Address::Nft(
nft_output.nft_address(output_with_meta.metadata().output_id()),
));
}
_ => {}
}
}
let mut processed_utxo_chains = Vec::new();
// Make the outputs response optional, because we don't know it yet for new required outputs
let mut utxo_chain_optional_response: Vec<(Address, Option<OutputWithMetadata>)> =
utxo_chains.iter_mut().map(|(a, o)| (*a, Some(o.clone()))).collect();
// Get alias or nft addresses when needed or just add the input again
while let Some((unlock_address, output_response)) = utxo_chain_optional_response.pop() {
// Don't request outputs for addresses where we already have the output
if processed_alias_nft_addresses.insert(unlock_address) {
match unlock_address {
Address::Alias(address) => {
let input_id = client.alias_output_id(*address.alias_id()).await?;
let input = client.get_output(&input_id).await?;
if let Output::Alias(alias_input) = input.output() {
// State transition if we add them to inputs
let alias_unlock_address = alias_input.state_controller_address();
// Add address to unprocessed_alias_nft_addresses so we get the required output there
// also
if alias_unlock_address.is_alias() || alias_unlock_address.is_nft() {
utxo_chain_optional_response.push((*alias_unlock_address, None));
}
processed_utxo_chains.push((*alias_unlock_address, input));
}
}
Address::Nft(address) => {
let input_id = client.nft_output_id(*address.nft_id()).await?;
let input = client.get_output(&input_id).await?;
if let Output::Nft(nft_input) = input.output() {
let unlock_address = nft_input
.unlock_conditions()
.locked_address(nft_input.address(), current_time);
// Add address to unprocessed_alias_nft_addresses so we get the required output there also
if unlock_address.is_alias() || unlock_address.is_nft() {
utxo_chain_optional_response.push((*unlock_address, None));
}
processed_utxo_chains.push((*unlock_address, input));
}
}
_ => {}
}
}
// Add if the output_response is available
if let Some(output_response) = output_response {
processed_utxo_chains.push((unlock_address, output_response));
}
}
*utxo_chains = processed_utxo_chains;
Ok(())
}
impl<'a> ClientBlockBuilder<'a> {
/// Get inputs for utxo chains
pub(crate) async fn get_utxo_chains_inputs(
&self,
outputs: impl Iterator<Item = &'a Output> + Clone + Send,
) -> Result<Vec<InputSigningData>> {
log::debug!("[get_utxo_chains_inputs]");
let client = self.client;
let bech32_hrp = client.get_bech32_hrp().await?;
let current_time = self.client.get_time_checked().await?;
let mut utxo_chains: Vec<(Address, OutputWithMetadata)> = Vec::new();
for output in outputs {
match output {
Output::Alias(alias_output) => {
// if the alias id is null then there can't be a previous output and it can also not be a
// governance transition
if !alias_output.alias_id().is_null() {
// Check if the transaction is a governance_transition, by checking if the new index is the same
// as the previous index
let output_id = client.alias_output_id(*alias_output.alias_id()).await?;
let input = client.get_output(&output_id).await?;
if let Output::Alias(alias_input) = input.output() {
// A governance transition is identified by an unchanged State Index in next
// state.
if alias_output.state_index() == alias_input.state_index() {
utxo_chains.push((*alias_input.governor_address(), input));
} else {
utxo_chains.push((*alias_input.state_controller_address(), input));
}
}
}
}
Output::Nft(nft_output) => {
// If the id is null then this output creates it and we can't have a previous output
if !nft_output.nft_id().is_null() {
let output_id = client.nft_output_id(*nft_output.nft_id()).await?;
let input = client.get_output(&output_id).await?;
if let Output::Nft(nft_input) = input.output() {
let unlock_address = nft_input
.unlock_conditions()
.locked_address(nft_output.address(), current_time);
utxo_chains.push((*unlock_address, input));
}
}
}
Output::Foundry(foundry_output) => {
// if it's the first foundry output, then we can't have it as input
if let Ok(output_id) = client.foundry_output_id(foundry_output.id()).await {
let input = client.get_output(&output_id).await?;
if let Output::Foundry(foundry_input_output) = input.output() {
utxo_chains.push((Address::Alias(*foundry_input_output.alias_address()), input));
}
}
}
_ => {}
}
}
// Get recursively owned alias or nft outputs
get_alias_and_nft_outputs_recursively(self.client, &mut utxo_chains).await?;
let mut utxo_chain_inputs = Vec::new();
for (unlock_address, output_with_meta) in utxo_chains {
let address_index_internal = match self.secret_manager {
Some(secret_manager) => {
match unlock_address {
Address::Ed25519(_) => Some(
search_address(
secret_manager,
bech32_hrp,
self.coin_type,
self.account_index,
self.input_range.clone(),
&unlock_address,
)
.await?,
),
// Alias and NFT addresses can't be generated from a private key
_ => None,
}
}
// Assuming default for offline signing
None => Some((0, false)),
};
utxo_chain_inputs.push(InputSigningData {
output: output_with_meta.output,
output_metadata: output_with_meta.metadata,
chain: address_index_internal.map(|(address_index, internal)| {
Bip44::new(self.coin_type)
.with_account(self.account_index)
.with_change(internal as _)
.with_address_index(address_index)
}),
});
}
Ok(utxo_chain_inputs)
}
}