use alloc::{collections::BTreeSet, string::ToString, vec::Vec};
use miden_lib::transaction::TransactionKernel;
use miden_objects::{accounts::AccountId, crypto::rand::FeltRng};
use miden_tx::auth::TransactionAuthenticator;
use winter_maybe_async::{maybe_async, maybe_await};
use crate::{
rpc::NodeRpcClient,
store::{InputNoteRecord, NoteFilter, OutputNoteRecord, Store},
Client, ClientError, IdPrefixFetchError,
};
mod import;
mod note_screener;
pub use miden_objects::{
notes::{
Note, NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteFile, NoteId,
NoteInclusionProof, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, NoteTag, NoteType,
Nullifier,
},
NoteError,
};
pub(crate) use note_screener::NoteScreener;
pub use note_screener::{NoteConsumability, NoteRelevance, NoteScreenerError};
impl<N: NodeRpcClient, R: FeltRng, S: Store, A: TransactionAuthenticator> Client<N, R, S, A> {
#[maybe_async]
pub fn get_input_notes(
&self,
filter: NoteFilter<'_>,
) -> Result<Vec<InputNoteRecord>, ClientError> {
maybe_await!(self.store.get_input_notes(filter)).map_err(|err| err.into())
}
#[maybe_async]
pub fn get_consumable_notes(
&self,
account_id: Option<AccountId>,
) -> Result<Vec<(InputNoteRecord, Vec<NoteConsumability>)>, ClientError> {
let commited_notes = maybe_await!(self.store.get_input_notes(NoteFilter::Committed))?;
let unconsumable_committed_note_ids: BTreeSet<NoteId> =
maybe_await!(self.store.get_notes_without_block_header())?
.into_iter()
.map(|note| note.id())
.collect();
let note_screener = NoteScreener::new(self.store.clone());
let mut relevant_notes = Vec::new();
for input_note in commited_notes {
if unconsumable_committed_note_ids.contains(&input_note.id()) {
continue;
}
let mut account_relevance =
maybe_await!(note_screener.check_relevance(&input_note.clone().try_into()?))?;
if let Some(account_id) = account_id {
account_relevance.retain(|(id, _)| *id == account_id);
}
if account_relevance.is_empty() {
continue;
}
relevant_notes.push((input_note, account_relevance));
}
Ok(relevant_notes)
}
#[maybe_async]
pub fn get_note_consumability(
&self,
note: InputNoteRecord,
) -> Result<Vec<NoteConsumability>, ClientError> {
let note_screener = NoteScreener::new(self.store.clone());
maybe_await!(note_screener.check_relevance(¬e.clone().try_into()?))
.map_err(|err| err.into())
}
#[maybe_async]
pub fn get_input_note(&self, note_id: NoteId) -> Result<InputNoteRecord, ClientError> {
Ok(maybe_await!(self.store.get_input_notes(NoteFilter::Unique(note_id)))?
.pop()
.expect("The vector always has one element for NoteFilter::Unique"))
}
#[maybe_async]
pub fn get_output_notes(
&self,
filter: NoteFilter<'_>,
) -> Result<Vec<OutputNoteRecord>, ClientError> {
maybe_await!(self.store.get_output_notes(filter)).map_err(|err| err.into())
}
#[maybe_async]
pub fn get_output_note(&self, note_id: NoteId) -> Result<OutputNoteRecord, ClientError> {
Ok(maybe_await!(self.store.get_output_notes(NoteFilter::Unique(note_id)))?
.pop()
.expect("The vector always has one element for NoteFilter::Unique"))
}
pub fn compile_note_script(&self, note_script_ast: &str) -> Result<NoteScript, ClientError> {
NoteScript::compile(note_script_ast, TransactionKernel::assembler())
.map_err(ClientError::NoteError)
}
}
#[maybe_async]
pub fn get_input_note_with_id_prefix<
N: NodeRpcClient,
R: FeltRng,
S: Store,
A: TransactionAuthenticator,
>(
client: &Client<N, R, S, A>,
note_id_prefix: &str,
) -> Result<InputNoteRecord, IdPrefixFetchError> {
let mut input_note_records = maybe_await!(client.get_input_notes(NoteFilter::All))
.map_err(|err| {
tracing::error!("Error when fetching all notes from the store: {err}");
IdPrefixFetchError::NoMatch(format!("note ID prefix {note_id_prefix}").to_string())
})?
.into_iter()
.filter(|note_record| note_record.id().to_hex().starts_with(note_id_prefix))
.collect::<Vec<_>>();
if input_note_records.is_empty() {
return Err(IdPrefixFetchError::NoMatch(
format!("note ID prefix {note_id_prefix}").to_string(),
));
}
if input_note_records.len() > 1 {
let input_note_record_ids = input_note_records
.iter()
.map(|input_note_record| input_note_record.id())
.collect::<Vec<_>>();
tracing::error!(
"Multiple notes found for the prefix {}: {:?}",
note_id_prefix,
input_note_record_ids
);
return Err(IdPrefixFetchError::MultipleMatches(
format!("note ID prefix {note_id_prefix}").to_string(),
));
}
Ok(input_note_records
.pop()
.expect("input_note_records should always have one element"))
}