pub(crate) mod addresses;
pub(crate) mod foundries;
pub(crate) mod options;
pub(crate) mod outputs;
pub(crate) mod transactions;
use std::collections::{HashMap, HashSet};
pub use self::options::SyncOptions;
use crate::{
client::secret::SecretManage,
types::block::{
address::{Address, AliasAddress, NftAddress, ToBech32Ext},
output::{FoundryId, Output, OutputId, OutputMetadata},
},
wallet::account::{
constants::MIN_SYNC_INTERVAL,
types::{AddressWithUnspentOutputs, OutputData},
Account, Balance,
},
};
impl<S: 'static + SecretManage> Account<S>
where
crate::wallet::Error: From<S::Error>,
{
pub async fn set_default_sync_options(&self, options: SyncOptions) -> crate::wallet::Result<()> {
#[cfg(feature = "storage")]
{
let index = *self.details().await.index();
let storage_manager = self.wallet.storage_manager.read().await;
storage_manager.set_default_sync_options(index, &options).await?;
}
*self.default_sync_options.lock().await = options;
Ok(())
}
pub async fn default_sync_options(&self) -> SyncOptions {
self.default_sync_options.lock().await.clone()
}
pub async fn sync(&self, options: Option<SyncOptions>) -> crate::wallet::Result<Balance> {
let options = match options {
Some(opt) => opt,
None => self.default_sync_options().await,
};
log::debug!("[SYNC] start syncing with {:?}", options);
let syc_start_time = instant::Instant::now();
let time_now = crate::utils::unix_timestamp_now().as_millis();
let mut last_synced = self.last_synced.lock().await;
log::debug!("[SYNC] last time synced before {}ms", time_now - *last_synced);
if !options.force_syncing && time_now - *last_synced < MIN_SYNC_INTERVAL {
log::debug!(
"[SYNC] synced within the latest {} ms, only calculating balance",
MIN_SYNC_INTERVAL
);
return self.balance().await;
}
self.sync_internal(&options).await?;
if options.sync_pending_transactions {
let confirmed_tx_with_unknown_output = self.sync_pending_transactions().await?;
if confirmed_tx_with_unknown_output {
log::debug!("[SYNC] a transaction for which no output is known got confirmed, syncing outputs again");
self.sync_internal(&options).await?;
}
};
let balance = self.balance().await?;
let time_now = crate::utils::unix_timestamp_now().as_millis();
*last_synced = time_now;
log::debug!("[SYNC] finished syncing in {:.2?}", syc_start_time.elapsed());
Ok(balance)
}
async fn sync_internal(&self, options: &SyncOptions) -> crate::wallet::Result<()> {
log::debug!("[SYNC] sync_internal");
let addresses_to_sync = self.get_addresses_to_sync(options).await?;
log::debug!("[SYNC] addresses_to_sync {}", addresses_to_sync.len());
let (spent_or_not_synced_output_ids, addresses_with_unspent_outputs, outputs_data): (
Vec<OutputId>,
Vec<AddressWithUnspentOutputs>,
Vec<OutputData>,
) = self.request_outputs_recursively(addresses_to_sync, options).await?;
log::debug!("[SYNC] spent_or_not_synced_outputs: {spent_or_not_synced_output_ids:?}");
let spent_or_unsynced_output_metadata_responses = self
.client()
.get_outputs_metadata_ignore_errors(&spent_or_not_synced_output_ids)
.await?;
let mut spent_or_unsynced_output_metadata_map: HashMap<OutputId, Option<OutputMetadata>> =
spent_or_not_synced_output_ids.into_iter().map(|o| (o, None)).collect();
for output_metadata_response in spent_or_unsynced_output_metadata_responses {
let output_id = output_metadata_response.output_id();
spent_or_unsynced_output_metadata_map.insert(*output_id, Some(output_metadata_response));
}
if options.sync_incoming_transactions {
let transaction_ids = outputs_data
.iter()
.map(|output| *output.output_id.transaction_id())
.collect();
self.request_incoming_transaction_data(transaction_ids).await?;
}
if options.sync_native_token_foundries {
let native_token_foundry_ids = outputs_data
.iter()
.filter_map(|output| output.output.native_tokens())
.flat_map(|native_tokens| {
native_tokens
.iter()
.map(|native_token| FoundryId::from(*native_token.token_id()))
})
.collect::<HashSet<_>>();
self.request_and_store_foundry_outputs(native_token_foundry_ids).await?;
}
self.update_account(
addresses_with_unspent_outputs,
outputs_data,
spent_or_unsynced_output_metadata_map,
options,
)
.await
}
async fn request_outputs_recursively(
&self,
addresses_to_sync: Vec<AddressWithUnspentOutputs>,
options: &SyncOptions,
) -> crate::wallet::Result<(Vec<OutputId>, Vec<AddressWithUnspentOutputs>, Vec<OutputData>)> {
let mut new_alias_and_nft_addresses = HashMap::new();
let (mut spent_or_not_synced_output_ids, mut addresses_with_unspent_outputs, mut outputs_data) =
(Vec::new(), Vec::new(), Vec::new());
loop {
let new_outputs_data = if new_alias_and_nft_addresses.is_empty() {
let (addresses_with_output_ids, spent_or_not_synced_output_ids_inner) = self
.get_output_ids_for_addresses(options, addresses_to_sync.clone())
.await?;
spent_or_not_synced_output_ids = spent_or_not_synced_output_ids_inner;
let (addresses_with_unspent_outputs_inner, outputs_data_inner) = self
.get_outputs_from_address_output_ids(addresses_with_output_ids)
.await?;
addresses_with_unspent_outputs = addresses_with_unspent_outputs_inner;
outputs_data.extend(outputs_data_inner.clone());
outputs_data_inner
} else {
let bech32_hrp = self.client().get_bech32_hrp().await?;
let mut new_outputs_data = Vec::new();
for (alias_or_nft_address, ed25519_address) in new_alias_and_nft_addresses {
let output_ids = self.get_output_ids_for_address(alias_or_nft_address, options).await?;
let address_with_unspent_outputs = addresses_with_unspent_outputs
.iter_mut()
.find(|a| a.address.inner == ed25519_address)
.ok_or_else(|| {
crate::wallet::Error::AddressNotFoundInAccount(ed25519_address.to_bech32(bech32_hrp))
})?;
address_with_unspent_outputs.output_ids.extend(output_ids.clone());
let new_outputs_data_inner = self.get_outputs(output_ids).await?;
let outputs_data_inner = self
.output_response_to_output_data(new_outputs_data_inner, address_with_unspent_outputs)
.await?;
outputs_data.extend(outputs_data_inner.clone());
new_outputs_data.extend(outputs_data_inner);
}
new_outputs_data
};
new_alias_and_nft_addresses = HashMap::new();
for output_data in new_outputs_data.iter() {
match &output_data.output {
Output::Alias(alias_output) => {
let alias_address = AliasAddress::from(alias_output.alias_id_non_null(&output_data.output_id));
new_alias_and_nft_addresses.insert(Address::Alias(alias_address), output_data.address);
}
Output::Nft(nft_output) => {
let nft_address = NftAddress::from(nft_output.nft_id_non_null(&output_data.output_id));
new_alias_and_nft_addresses.insert(Address::Nft(nft_address), output_data.address);
}
_ => {}
}
}
log::debug!("[SYNC] new_alias_and_nft_addresses: {new_alias_and_nft_addresses:?}");
if new_alias_and_nft_addresses.is_empty() {
break;
}
}
let unspent_output_ids: HashSet<OutputId> = HashSet::from_iter(outputs_data.iter().map(|o| o.output_id));
spent_or_not_synced_output_ids.retain(|o| !unspent_output_ids.contains(o));
Ok((
spent_or_not_synced_output_ids,
addresses_with_unspent_outputs,
outputs_data,
))
}
}