use std::collections::{hash_map::Values, HashSet};
#[cfg(feature = "events")]
use crate::wallet::events::types::{TransactionProgressEvent, WalletEvent};
use crate::{
client::{
api::input_selection::{is_alias_transition, Burn, InputSelection, Selected},
secret::{types::InputSigningData, SecretManage},
},
types::block::{
address::Address,
output::{Output, OutputId},
},
wallet::account::{
operations::helpers::time::can_output_be_unlocked_forever_from_now_on, Account, AccountDetails, OutputData,
},
};
impl<S: 'static + SecretManage> Account<S>
where
crate::wallet::Error: From<S::Error>,
{
pub(crate) async fn select_inputs(
&self,
outputs: Vec<Output>,
custom_inputs: Option<HashSet<OutputId>>,
mandatory_inputs: Option<HashSet<OutputId>>,
remainder_address: Option<Address>,
burn: Option<&Burn>,
) -> crate::wallet::Result<Selected> {
log::debug!("[TRANSACTION] select_inputs");
#[cfg(feature = "participation")]
let voting_output = self.get_voting_output().await?;
let mut account_details = self.details_mut().await;
let protocol_parameters = self.client().get_protocol_parameters().await?;
#[cfg(feature = "events")]
self.emit(
account_details.index,
WalletEvent::TransactionProgress(TransactionProgressEvent::SelectingInputs),
)
.await;
let current_time = self.client().get_time_checked().await?;
#[allow(unused_mut)]
let mut forbidden_inputs = account_details.locked_outputs.clone();
let addresses = account_details
.public_addresses()
.iter()
.chain(account_details.internal_addresses().iter())
.map(|address| *address.address.as_ref())
.collect::<Vec<_>>();
#[cfg(feature = "participation")]
if let Some(voting_output) = &voting_output {
let required = mandatory_inputs.as_ref().map_or(false, |mandatory_inputs| {
mandatory_inputs.contains(&voting_output.output_id)
});
if !required {
forbidden_inputs.insert(voting_output.output_id);
}
}
let available_outputs_signing_data = filter_inputs(
&account_details,
account_details.unspent_outputs.values(),
current_time,
&outputs,
burn,
custom_inputs.as_ref(),
mandatory_inputs.as_ref(),
)?;
if let Some(custom_inputs) = custom_inputs {
for input in custom_inputs.iter() {
if account_details.locked_outputs.contains(input) {
return Err(crate::wallet::Error::CustomInput(format!(
"provided custom input {input} is already used in another transaction",
)));
}
}
let mut input_selection = InputSelection::new(
available_outputs_signing_data,
outputs,
addresses,
protocol_parameters.clone(),
)
.required_inputs(custom_inputs)
.forbidden_inputs(forbidden_inputs);
if let Some(address) = remainder_address {
input_selection = input_selection.remainder_address(address);
}
if let Some(burn) = burn {
input_selection = input_selection.burn(burn.clone());
}
let selected_transaction_data = input_selection.select()?;
for output in &selected_transaction_data.inputs {
account_details.locked_outputs.insert(*output.output_id());
}
return Ok(selected_transaction_data);
} else if let Some(mandatory_inputs) = mandatory_inputs {
for input in mandatory_inputs.iter() {
if account_details.locked_outputs.contains(input) {
return Err(crate::wallet::Error::CustomInput(format!(
"provided custom input {input} is already used in another transaction",
)));
}
}
let mut input_selection = InputSelection::new(
available_outputs_signing_data,
outputs,
addresses,
protocol_parameters.clone(),
)
.required_inputs(mandatory_inputs)
.forbidden_inputs(forbidden_inputs);
if let Some(address) = remainder_address {
input_selection = input_selection.remainder_address(address);
}
if let Some(burn) = burn {
input_selection = input_selection.burn(burn.clone());
}
let selected_transaction_data = input_selection.select()?;
for output in &selected_transaction_data.inputs {
account_details.locked_outputs.insert(*output.output_id());
}
for output in &selected_transaction_data.inputs {
account_details.locked_outputs.insert(*output.output_id());
}
return Ok(selected_transaction_data);
}
let mut input_selection = InputSelection::new(
available_outputs_signing_data,
outputs,
addresses,
protocol_parameters.clone(),
)
.forbidden_inputs(forbidden_inputs);
if let Some(address) = remainder_address {
input_selection = input_selection.remainder_address(address);
}
if let Some(burn) = burn {
input_selection = input_selection.burn(burn.clone());
}
let selected_transaction_data = match input_selection.select() {
Ok(r) => r,
Err(e) => return Err(e.into()),
};
for output in &selected_transaction_data.inputs {
log::debug!("[TRANSACTION] locking: {}", output.output_id());
account_details.locked_outputs.insert(*output.output_id());
}
Ok(selected_transaction_data)
}
}
#[allow(clippy::too_many_arguments)]
fn filter_inputs(
account: &AccountDetails,
available_outputs: Values<'_, OutputId, OutputData>,
current_time: u32,
outputs: &[Output],
burn: Option<&Burn>,
custom_inputs: Option<&HashSet<OutputId>>,
mandatory_inputs: Option<&HashSet<OutputId>>,
) -> crate::wallet::Result<Vec<InputSigningData>> {
let mut available_outputs_signing_data = Vec::new();
for output_data in available_outputs {
if !custom_inputs
.map(|inputs| inputs.contains(&output_data.output_id))
.unwrap_or(false)
&& !mandatory_inputs
.map(|inputs| inputs.contains(&output_data.output_id))
.unwrap_or(false)
{
let output_can_be_unlocked_now_and_in_future = can_output_be_unlocked_forever_from_now_on(
&account.addresses_with_unspent_outputs,
&output_data.output,
current_time,
);
if !output_can_be_unlocked_now_and_in_future {
continue;
}
}
let alias_state_transition = is_alias_transition(&output_data.output, output_data.output_id, outputs, burn);
if let Some(available_input) = output_data.input_signing_data(account, current_time, alias_state_transition)? {
available_outputs_signing_data.push(available_input);
}
}
Ok(available_outputs_signing_data)
}