use std::cmp;
use crate::{
client::secret::{GenerateAddressOptions, SecretManage},
wallet::account::{operations::syncing::SyncOptions, types::AddressWithUnspentOutputs, Account},
};
impl<S: 'static + SecretManage> Account<S>
where
crate::wallet::Error: From<S::Error>,
{
pub(crate) async fn search_addresses_with_outputs(
&self,
mut address_gap_limit: u32,
sync_options: Option<SyncOptions>,
) -> crate::wallet::Result<usize> {
log::debug!("[search_addresses_with_outputs]");
let mut sync_options = match sync_options {
Some(opt) => opt,
None => self.default_sync_options().await.clone(),
};
let (highest_public_address_index, highest_internal_address_index) = {
let account_details = self.details().await;
(
account_details
.public_addresses
.last()
.map(|a| a.key_index)
.expect("account needs to have a public address"),
account_details.internal_addresses.last().map(|a| a.key_index),
)
};
if sync_options.address_start_index != 0 {
let mut address_amount_to_generate =
sync_options.address_start_index.abs_diff(highest_public_address_index);
address_amount_to_generate = address_amount_to_generate.saturating_sub(1);
log::debug!(
"[search_addresses_with_outputs] generate {address_amount_to_generate} public addresses below the start index"
);
self.generate_ed25519_addresses(address_amount_to_generate, None)
.await?;
}
if sync_options.address_start_index_internal != 0 {
let mut address_amount_to_generate = sync_options
.address_start_index_internal
.abs_diff(highest_internal_address_index.unwrap_or(0));
if address_amount_to_generate > 0 && highest_internal_address_index.is_some() {
address_amount_to_generate -= 1;
}
log::debug!(
"[search_addresses_with_outputs] generate {address_amount_to_generate} internal addresses below the start index"
);
self.generate_ed25519_addresses(address_amount_to_generate, Some(GenerateAddressOptions::internal()))
.await?;
}
let mut address_gap_limit_internal = address_gap_limit;
let mut latest_outputs_count = 0;
loop {
let (highest_public_address_index, highest_internal_address_index) = {
let account_details = self.details().await;
(
account_details
.public_addresses
.last()
.map(|a| a.key_index)
.expect("account needs to have a public address"),
account_details.internal_addresses.last().map(|a| a.key_index),
)
};
log::debug!(
"[search_addresses_with_outputs] address_gap_limit: {address_gap_limit}, address_gap_limit_internal: {address_gap_limit_internal}"
);
let addresses = self.generate_ed25519_addresses(address_gap_limit, None).await?;
let internal_addresses = self
.generate_ed25519_addresses(address_gap_limit_internal, Some(GenerateAddressOptions::internal()))
.await?;
let address_start_index = addresses
.first()
.map(|a| {
if a.key_index == 1 { 0 } else { a.key_index }
})
.unwrap_or(highest_public_address_index + 1);
let address_start_index_internal = internal_addresses
.first()
.map(|a| a.key_index)
.unwrap_or_else(|| highest_internal_address_index.unwrap_or(0) + 1);
sync_options.force_syncing = true;
sync_options.address_start_index = address_start_index;
sync_options.address_start_index_internal = address_start_index_internal;
self.sync(Some(sync_options.clone())).await?;
let output_count = self.details().await.unspent_outputs.len();
if output_count <= latest_outputs_count {
break;
}
latest_outputs_count = output_count;
let account_details = self.details().await;
let highest_address_index = account_details
.public_addresses
.iter()
.max_by_key(|a| *a.key_index())
.map(|a| *a.key_index())
.expect("account needs to have at least one public address");
let highest_address_index_internal = account_details
.internal_addresses
.iter()
.max_by_key(|a| *a.key_index())
.map(|a| *a.key_index())
.unwrap_or(0);
drop(account_details);
let addresses_with_unspent_outputs = self.addresses_with_unspent_outputs().await?;
let (addresses_with_outputs_internal, address_with_outputs): (
Vec<&AddressWithUnspentOutputs>,
Vec<&AddressWithUnspentOutputs>,
) = addresses_with_unspent_outputs.iter().partition(|a| a.internal);
let latest_address_index_with_outputs = address_with_outputs
.iter()
.max_by_key(|a| *a.key_index())
.map(|a| *a.key_index() as i64)
.unwrap_or(-1);
let latest_address_index_with_outputs_internal = addresses_with_outputs_internal
.iter()
.max_by_key(|a| *a.key_index())
.map(|a| *a.key_index() as i64)
.unwrap_or(-1);
log::debug!(
"new highest_address_index: {highest_address_index}, internal: {highest_address_index_internal}"
);
log::debug!(
"new latest_address_index_with_outputs: {latest_address_index_with_outputs:?}, internal: {latest_address_index_with_outputs_internal:?}"
);
let empty_addresses_in_row = (highest_address_index as i64 - latest_address_index_with_outputs) as u32;
let empty_addresses_in_row_internal =
(highest_address_index_internal as i64 - latest_address_index_with_outputs_internal) as u32;
log::debug!(
"new empty_addresses_in_row: {empty_addresses_in_row}, internal: {empty_addresses_in_row_internal}"
);
if empty_addresses_in_row > address_gap_limit {
log::debug!("empty_addresses_in_row: {empty_addresses_in_row}, setting address_gap_limit to 0");
address_gap_limit = 0;
} else {
address_gap_limit -= empty_addresses_in_row;
}
if empty_addresses_in_row_internal > address_gap_limit_internal {
log::debug!(
"empty_addresses_in_row_internal: {empty_addresses_in_row_internal}, setting address_gap_limit_internal to 0"
);
address_gap_limit_internal = 0;
} else {
address_gap_limit_internal -= empty_addresses_in_row_internal;
}
log::debug!("new address_gap_limit: {address_gap_limit}, internal: {address_gap_limit_internal}");
if address_gap_limit == 0 && address_gap_limit_internal == 0 {
break;
}
}
self.clean_account_after_recovery(highest_public_address_index, highest_internal_address_index)
.await;
#[cfg(feature = "storage")]
{
log::debug!(
"[search_addresses_with_outputs] storing account {} with new synced data",
self.alias().await
);
self.save(None).await?;
}
Ok(latest_outputs_count)
}
async fn clean_account_after_recovery(
&self,
old_highest_public_address_index: u32,
old_highest_internal_address_index: Option<u32>,
) {
let mut account_details = self.details_mut().await;
let (internal_addresses_with_unspent_outputs, public_addresses_with_spent_outputs): (
Vec<&AddressWithUnspentOutputs>,
Vec<&AddressWithUnspentOutputs>,
) = account_details
.addresses_with_unspent_outputs()
.iter()
.partition(|address| address.internal);
let highest_public_index_with_outputs = public_addresses_with_spent_outputs
.iter()
.map(|a| a.key_index)
.max()
.unwrap_or(0);
let highest_internal_index_with_outputs = internal_addresses_with_unspent_outputs
.iter()
.map(|a| a.key_index)
.max();
let new_latest_public_index = cmp::max(highest_public_index_with_outputs, old_highest_public_address_index);
account_details.public_addresses = account_details
.public_addresses
.clone()
.into_iter()
.filter(|a| a.key_index <= new_latest_public_index)
.collect();
account_details.internal_addresses =
if old_highest_internal_address_index.is_none() && highest_internal_index_with_outputs.is_none() {
Vec::new()
} else {
let new_latest_internal_index = cmp::max(
highest_internal_index_with_outputs.unwrap_or(0),
old_highest_internal_address_index.unwrap_or(0),
);
account_details
.internal_addresses
.clone()
.into_iter()
.filter(|a| a.key_index <= new_latest_internal_index)
.collect()
};
}
}