use std::cmp;
use std::collections::{BTreeSet, HashMap};
use std::ops::Range;
use tokio::sync::mpsc;
use zcash_keys::keys::UnifiedFullViewingKey;
use zcash_protocol::consensus::{self, BlockHeight};
use zcash_transparent::keys::NonHardenedChildIndex;
use zip32::AccountId;
use crate::client::{self, FetchRequest};
use crate::config::TransparentAddressDiscovery;
use crate::error::SyncError;
use crate::keys;
use crate::keys::transparent::{TransparentAddressId, TransparentScope};
use crate::wallet::traits::SyncWallet;
use crate::wallet::{KeyIdInterface, ScanTarget};
use super::MAX_REORG_ALLOWANCE;
pub(crate) async fn update_addresses_and_scan_targets<W: SyncWallet>(
consensus_parameters: &impl consensus::Parameters,
wallet: &mut W,
fetch_request_sender: mpsc::UnboundedSender<FetchRequest>,
ufvks: &HashMap<AccountId, UnifiedFullViewingKey>,
last_known_chain_height: BlockHeight,
chain_height: BlockHeight,
config: TransparentAddressDiscovery,
) -> Result<(), SyncError<W::Error>> {
if !config.scopes.external && !config.scopes.internal && !config.scopes.refund {
return Ok(());
}
let wallet_addresses = wallet
.get_transparent_addresses_mut()
.map_err(SyncError::WalletError)?;
let mut scan_targets: BTreeSet<ScanTarget> = BTreeSet::new();
let sapling_activation_height = consensus_parameters
.activation_height(consensus::NetworkUpgrade::Sapling)
.expect("sapling activation height should always return Some");
let block_range_start = last_known_chain_height.saturating_sub(MAX_REORG_ALLOWANCE) + 1;
let checked_block_range_start = match block_range_start.cmp(&sapling_activation_height) {
cmp::Ordering::Greater | cmp::Ordering::Equal => block_range_start,
cmp::Ordering::Less => sapling_activation_height,
};
let block_range = Range {
start: checked_block_range_start,
end: chain_height + 1,
};
for address in wallet_addresses.values() {
let transactions = client::get_transparent_address_transactions(
fetch_request_sender.clone(),
consensus_parameters,
address.clone(),
block_range.clone(),
)
.await?;
for (height, tx) in &transactions {
scan_targets.insert(ScanTarget {
block_height: *height,
txid: tx.txid(),
narrow_scan_area: true,
});
}
}
let mut scopes = Vec::new();
if config.scopes.external {
scopes.push(TransparentScope::External);
}
if config.scopes.internal {
scopes.push(TransparentScope::Internal);
}
if config.scopes.refund {
scopes.push(TransparentScope::Refund);
}
for (account_id, ufvk) in ufvks {
if let Some(account_pubkey) = ufvk.transparent() {
for scope in &scopes {
let mut address_index = if let Some(id) = wallet_addresses
.keys()
.filter(|id| id.account_id() == *account_id && id.scope() == *scope)
.next_back()
{
id.address_index().index() + 1
} else {
0
};
let mut unused_address_count: usize = 0;
let mut addresses: Vec<(TransparentAddressId, String)> = Vec::new();
while unused_address_count < config.gap_limit as usize {
let address_id = TransparentAddressId::new(
*account_id,
*scope,
NonHardenedChildIndex::from_index(address_index)
.expect("all non-hardened addresses in use!"),
);
let address = keys::transparent::derive_address(
consensus_parameters,
account_pubkey,
address_id,
)
.map_err(SyncError::TransparentAddressDerivationError)?;
addresses.push((address_id, address.clone()));
let transactions = client::get_transparent_address_transactions(
fetch_request_sender.clone(),
consensus_parameters,
address,
block_range.clone(),
)
.await?;
if transactions.is_empty() {
unused_address_count += 1;
} else {
for (height, tx) in &transactions {
scan_targets.insert(ScanTarget {
block_height: *height,
txid: tx.txid(),
narrow_scan_area: true,
});
}
unused_address_count = 0;
}
address_index += 1;
}
addresses.truncate(addresses.len() - config.gap_limit as usize);
for (id, address) in addresses {
wallet_addresses.insert(id, address);
}
}
}
}
wallet
.get_sync_state_mut()
.map_err(SyncError::WalletError)?
.scan_targets
.append(&mut scan_targets);
Ok(())
}