use std::str::FromStr;
use crypto::keys::slip10::Chain;
use instant::Instant;
use crate::{
client::Client,
types::{
api::core::response::OutputWithMetadataResponse,
block::{
input::Input,
output::{dto::OutputDto, Output, OutputId},
payload::{
transaction::{TransactionEssence, TransactionId},
Payload, TransactionPayload,
},
},
},
wallet::{
account::{build_transaction_from_payload_and_inputs, types::OutputData, Account, AddressWithUnspentOutputs},
task,
},
};
impl Account {
pub(crate) async fn output_response_to_output_data(
&self,
output_responses: Vec<OutputWithMetadataResponse>,
associated_address: &AddressWithUnspentOutputs,
) -> crate::wallet::Result<Vec<OutputData>> {
log::debug!("[SYNC] convert output_responses");
let network_id = self.client.get_network_id().await?;
let mut outputs = Vec::new();
let token_supply = self.client.get_token_supply().await?;
let account_details = self.read().await;
for output_response in output_responses {
let output = Output::try_from_dto(&output_response.output, token_supply)?;
let transaction_id = TransactionId::from_str(&output_response.metadata.transaction_id)?;
let remainder = account_details
.transactions
.get(&transaction_id)
.map_or(false, |tx| !tx.incoming);
let chain = Chain::from_u32_hardened(vec![
44,
account_details.coin_type,
account_details.index,
associated_address.internal as u32,
associated_address.key_index,
]);
outputs.push(OutputData {
output_id: OutputId::new(transaction_id, output_response.metadata.output_index)?,
metadata: output_response.metadata.clone(),
output,
is_spent: output_response.metadata.is_spent,
address: associated_address.address.inner,
network_id,
remainder,
chain: Some(chain),
});
}
Ok(outputs)
}
pub(crate) async fn get_outputs(
&self,
output_ids: Vec<OutputId>,
) -> crate::wallet::Result<Vec<OutputWithMetadataResponse>> {
log::debug!("[SYNC] start get_outputs");
let get_outputs_start_time = Instant::now();
let mut outputs = Vec::new();
let mut unknown_outputs = Vec::new();
let mut unspent_outputs = Vec::new();
let mut account_details = self.write().await;
for output_id in output_ids {
match account_details.outputs.get_mut(&output_id) {
Some(output_data) => {
output_data.is_spent = false;
unspent_outputs.push((output_id, output_data.clone()));
outputs.push(OutputWithMetadataResponse {
metadata: output_data.metadata.clone(),
output: OutputDto::from(&output_data.output),
});
}
None => unknown_outputs.push(output_id),
}
}
for (output_id, output_data) in unspent_outputs {
account_details.unspent_outputs.insert(output_id, output_data);
}
drop(account_details);
if !unknown_outputs.is_empty() {
outputs.extend(self.client.get_outputs(unknown_outputs).await?);
}
log::debug!(
"[SYNC] finished get_outputs in {:.2?}",
get_outputs_start_time.elapsed()
);
Ok(outputs)
}
pub(crate) async fn request_incoming_transaction_data(
&self,
transaction_ids: Vec<TransactionId>,
) -> crate::wallet::Result<()> {
log::debug!("[SYNC] request_incoming_transaction_data");
for transaction_ids_chunk in transaction_ids.chunks(100).map(|x: &[TransactionId]| x.to_vec()) {
let mut tasks = Vec::new();
let account_details = self.read().await;
for transaction_id in transaction_ids_chunk {
if account_details.transactions.contains_key(&transaction_id)
|| account_details.incoming_transactions.contains_key(&transaction_id)
|| account_details
.inaccessible_incoming_transactions
.contains(&transaction_id)
{
continue;
}
let client = self.client.clone();
tasks.push(async move {
task::spawn(async move {
match client.get_included_block(&transaction_id).await {
Ok(block) => {
if let Some(Payload::Transaction(transaction_payload)) = block.payload() {
let inputs =
get_inputs_for_transaction_payload(&client, transaction_payload).await?;
let transaction = build_transaction_from_payload_and_inputs(
transaction_id,
*transaction_payload.clone(),
inputs,
)?;
Ok((transaction_id, Some(transaction)))
} else {
Ok((transaction_id, None))
}
}
Err(crate::client::Error::NotFound(_)) => Ok((transaction_id, None)),
Err(e) => Err(crate::wallet::Error::Client(e.into())),
}
})
.await
});
}
drop(account_details);
let results = futures::future::try_join_all(tasks).await?;
let mut account_details = self.write().await;
for res in results {
match res? {
(transaction_id, Some(transaction)) => {
account_details
.incoming_transactions
.insert(transaction_id, transaction);
}
(transaction_id, None) => {
log::debug!("[SYNC] adding {transaction_id} to inaccessible_incoming_transactions");
account_details
.inaccessible_incoming_transactions
.insert(transaction_id);
}
}
}
}
Ok(())
}
}
pub(crate) async fn get_inputs_for_transaction_payload(
client: &Client,
transaction_payload: &TransactionPayload,
) -> crate::wallet::Result<Vec<OutputWithMetadataResponse>> {
let TransactionEssence::Regular(essence) = transaction_payload.essence();
let mut output_ids = Vec::new();
for input in essence.inputs() {
if let Input::Utxo(input) = input {
output_ids.push(*input.output_id());
}
}
client.try_get_outputs(output_ids).await.map_err(|e| e.into())
}