iota_sdk/wallet/account/operations/syncing/
outputs.rs1use crypto::keys::bip44::Bip44;
5use instant::Instant;
6
7use crate::{
8 client::{secret::SecretManage, Client},
9 types::{
10 api::core::response::OutputWithMetadataResponse,
11 block::{
12 input::Input,
13 output::{OutputId, OutputWithMetadata},
14 payload::{
15 transaction::{TransactionEssence, TransactionId},
16 Payload, TransactionPayload,
17 },
18 },
19 },
20 wallet::{
21 account::{build_transaction_from_payload_and_inputs, types::OutputData, Account, AddressWithUnspentOutputs},
22 task,
23 },
24};
25
26impl<S: 'static + SecretManage> Account<S>
27where
28 crate::wallet::Error: From<S::Error>,
29{
30 pub(crate) async fn output_response_to_output_data(
32 &self,
33 outputs_with_meta: Vec<OutputWithMetadata>,
34 associated_address: &AddressWithUnspentOutputs,
35 ) -> crate::wallet::Result<Vec<OutputData>> {
36 log::debug!("[SYNC] convert output_responses");
37 let network_id = self.client().get_network_id().await?;
39 let account_details = self.details().await;
40
41 Ok(outputs_with_meta
42 .into_iter()
43 .map(|output_with_meta| {
44 let remainder = account_details
47 .transactions
48 .get(output_with_meta.metadata().transaction_id())
49 .map_or(false, |tx| !tx.incoming);
50
51 let chain = Bip44::new(account_details.coin_type)
53 .with_account(account_details.index)
54 .with_change(associated_address.internal as _)
55 .with_address_index(associated_address.key_index);
56
57 OutputData {
58 output_id: output_with_meta.metadata().output_id().to_owned(),
59 metadata: *output_with_meta.metadata(),
60 output: output_with_meta.output().clone(),
61 is_spent: output_with_meta.metadata().is_spent(),
62 address: associated_address.address.inner,
63 network_id,
64 remainder,
65 chain: Some(chain),
66 }
67 })
68 .collect())
69 }
70
71 pub(crate) async fn get_outputs(
74 &self,
75 output_ids: Vec<OutputId>,
76 ) -> crate::wallet::Result<Vec<OutputWithMetadata>> {
77 log::debug!("[SYNC] start get_outputs");
78 let get_outputs_start_time = Instant::now();
79 let mut outputs = Vec::new();
80 let mut unknown_outputs = Vec::new();
81 let mut unspent_outputs = Vec::new();
82 let mut account_details = self.details_mut().await;
83
84 for output_id in output_ids {
85 match account_details.outputs.get_mut(&output_id) {
86 Some(output_data) => {
88 output_data.is_spent = false;
89 unspent_outputs.push((output_id, output_data.clone()));
90 outputs.push(OutputWithMetadata::new(
91 output_data.output.clone(),
92 output_data.metadata,
93 ));
94 }
95 None => unknown_outputs.push(output_id),
96 }
97 }
98 for (output_id, output_data) in unspent_outputs {
101 account_details.unspent_outputs.insert(output_id, output_data);
102 }
103
104 drop(account_details);
105
106 if !unknown_outputs.is_empty() {
107 outputs.extend(self.client().get_outputs(&unknown_outputs).await?);
108 }
109
110 log::debug!(
111 "[SYNC] finished get_outputs in {:.2?}",
112 get_outputs_start_time.elapsed()
113 );
114
115 Ok(outputs)
116 }
117
118 pub(crate) async fn request_incoming_transaction_data(
122 &self,
123 mut transaction_ids: Vec<TransactionId>,
124 ) -> crate::wallet::Result<()> {
125 log::debug!("[SYNC] request_incoming_transaction_data");
126
127 let account_details = self.details().await;
128 transaction_ids.retain(|transaction_id| {
129 !(account_details.transactions.contains_key(transaction_id)
130 || account_details.incoming_transactions.contains_key(transaction_id)
131 || account_details
132 .inaccessible_incoming_transactions
133 .contains(transaction_id))
134 });
135 drop(account_details);
136
137 let results =
139 futures::future::try_join_all(transaction_ids.chunks(100).map(|x| x.to_vec()).map(|transaction_ids| {
140 let client = self.client().clone();
141 async move {
142 task::spawn(async move {
143 futures::future::try_join_all(transaction_ids.iter().map(|transaction_id| async {
144 let transaction_id = *transaction_id;
145 match client.get_included_block(&transaction_id).await {
146 Ok(block) => {
147 if let Some(Payload::Transaction(transaction_payload)) = block.payload() {
148 let inputs_with_meta =
149 get_inputs_for_transaction_payload(&client, transaction_payload).await?;
150 let inputs_response: Vec<OutputWithMetadataResponse> = inputs_with_meta
151 .into_iter()
152 .map(OutputWithMetadataResponse::from)
153 .collect();
154
155 let transaction = build_transaction_from_payload_and_inputs(
156 transaction_id,
157 *transaction_payload.clone(),
158 inputs_response,
159 )?;
160
161 Ok((transaction_id, Some(transaction)))
162 } else {
163 Ok((transaction_id, None))
164 }
165 }
166 Err(crate::client::Error::Node(crate::client::node_api::error::Error::NotFound(_))) => {
167 Ok((transaction_id, None))
168 }
169 Err(e) => Err(crate::wallet::Error::Client(e.into())),
170 }
171 }))
172 .await
173 })
174 .await?
175 }
176 }))
177 .await?;
178
179 let mut account_details = self.details_mut().await;
181 for (transaction_id, txn) in results.into_iter().flatten() {
182 if let Some(transaction) = txn {
183 account_details
184 .incoming_transactions
185 .insert(transaction_id, transaction);
186 } else {
187 log::debug!("[SYNC] adding {transaction_id} to inaccessible_incoming_transactions");
188 account_details
191 .inaccessible_incoming_transactions
192 .insert(transaction_id);
193 }
194 }
195
196 Ok(())
197 }
198}
199
200pub(crate) async fn get_inputs_for_transaction_payload(
202 client: &Client,
203 transaction_payload: &TransactionPayload,
204) -> crate::wallet::Result<Vec<OutputWithMetadata>> {
205 let TransactionEssence::Regular(essence) = transaction_payload.essence();
206
207 let output_ids = essence
208 .inputs()
209 .iter()
210 .filter_map(|input| {
211 if let Input::Utxo(input) = input {
212 Some(*input.output_id())
213 } else {
214 None
215 }
216 })
217 .collect::<Vec<_>>();
218
219 client
220 .get_outputs_ignore_errors(&output_ids)
221 .await
222 .map_err(|e| e.into())
223}