1use alloc::{sync::Arc, vec::Vec};
2use core::fmt;
3
4use miden_lib::{account::interface::AccountInterface, note::well_known_note::WellKnownNote};
5use miden_objects::{
6 AccountError, AssetError,
7 account::{Account, AccountId},
8 assembly::DefaultSourceManager,
9 note::{Note, NoteId},
10 transaction::{InputNote, InputNotes},
11};
12use miden_tx::{
13 NoteAccountExecution, NoteConsumptionChecker, TransactionExecutor, TransactionExecutorError,
14 TransactionMastStore,
15};
16use thiserror::Error;
17
18use crate::{
19 store::{Store, StoreError},
20 transaction::{TransactionRequestBuilder, TransactionRequestError},
21};
22
23#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
25pub enum NoteRelevance {
26 Now,
28 After(u32),
30}
31
32pub type NoteConsumability = (AccountId, NoteRelevance);
37
38impl fmt::Display for NoteRelevance {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 NoteRelevance::Now => write!(f, "Now"),
42 NoteRelevance::After(height) => write!(f, "After block {height}"),
43 }
44 }
45}
46
47pub struct NoteScreener<'a> {
54 store: Arc<dyn Store>,
56 consumption_checker: NoteConsumptionChecker<'a>,
58 mast_store: Arc<TransactionMastStore>,
60}
61
62impl<'a> NoteScreener<'a> {
63 pub fn new(
64 store: Arc<dyn Store>,
65 tx_executor: &'a TransactionExecutor,
66 mast_store: Arc<TransactionMastStore>,
67 ) -> Self {
68 Self {
69 store,
70 consumption_checker: NoteConsumptionChecker::new(tx_executor),
71 mast_store,
72 }
73 }
74
75 pub async fn check_relevance(
84 &self,
85 note: &Note,
86 ) -> Result<Vec<NoteConsumability>, NoteScreenerError> {
87 let mut note_relevances = vec![];
88 for id in self.store.get_account_ids().await? {
89 let account_record = self
90 .store
91 .get_account(id)
92 .await?
93 .ok_or(NoteScreenerError::AccountDataNotFound(id))?;
94
95 match self.check_standard_consumability(account_record.account(), note).await {
96 Ok(Some(relevance)) => {
97 note_relevances.push((id, relevance));
98 },
99 Ok(None) => {
100 let script_root = note.script().root();
103
104 if script_root == WellKnownNote::P2IDR.script_root() {
105 if let Some(relevance) = Self::check_p2idr_recall_consumability(note, &id)?
106 {
107 note_relevances.push((id, relevance));
108 }
109 }
110 },
111 Err(_) => {},
114 }
115 }
116
117 Ok(note_relevances)
118 }
119
120 async fn check_standard_consumability(
123 &self,
124 account: &Account,
125 note: &Note,
126 ) -> Result<Option<NoteRelevance>, NoteScreenerError> {
127 let transaction_request =
128 TransactionRequestBuilder::new().build_consume_notes(vec![note.id()])?;
129
130 let tx_script =
131 transaction_request.build_transaction_script(&AccountInterface::from(account), true)?;
132
133 let tx_args = transaction_request.clone().into_transaction_args(tx_script, vec![]);
134 let input_notes = InputNotes::new(vec![InputNote::unauthenticated(note.clone())])
135 .expect("Single note should be valid");
136
137 self.mast_store.load_transaction_code(account.code(), &input_notes, &tx_args);
138
139 if let NoteAccountExecution::Success = self
140 .consumption_checker
141 .check_notes_consumability(
142 account.id(),
143 self.store.get_sync_height().await?,
144 input_notes,
145 tx_args,
146 Arc::new(DefaultSourceManager::default()),
147 )
148 .await?
149 {
150 return Ok(Some(NoteRelevance::Now));
151 }
152
153 Ok(None)
154 }
155
156 fn check_p2idr_recall_consumability(
159 note: &Note,
160 account_id: &AccountId,
161 ) -> Result<Option<NoteRelevance>, NoteScreenerError> {
162 let note_inputs = note.inputs().values();
163 if note_inputs.len() != 3 {
164 return Err(InvalidNoteInputsError::WrongNumInputs(note.id(), 3).into());
165 }
166
167 let recall_height_felt = note_inputs[2];
168
169 let sender = note.metadata().sender();
170 let recall_height: u32 = recall_height_felt.as_int().try_into().map_err(|_err| {
171 InvalidNoteInputsError::BlockNumberError(note.id(), recall_height_felt.as_int())
172 })?;
173
174 if sender == *account_id {
175 Ok(Some(NoteRelevance::After(recall_height)))
176 } else {
177 Ok(None)
178 }
179 }
180}
181
182#[derive(Debug, Error)]
187pub enum NoteScreenerError {
188 #[error("error while processing note inputs")]
189 InvalidNoteInputsError(#[from] InvalidNoteInputsError),
190 #[error("account data wasn't found for account id {0}")]
191 AccountDataNotFound(AccountId),
192 #[error("error while fetching data from the store")]
193 StoreError(#[from] StoreError),
194 #[error("error while checking consume transaction")]
195 TransactionExecutionError(#[from] TransactionExecutorError),
196 #[error("error while building consume transaction request")]
197 TransactionRequestError(#[from] TransactionRequestError),
198}
199
200#[derive(Debug, Error)]
201pub enum InvalidNoteInputsError {
202 #[error("account error for note with id {0}: {1}")]
203 AccountError(NoteId, AccountError),
204 #[error("asset error for note with id {0}: {1}")]
205 AssetError(NoteId, AssetError),
206 #[error("expected {1} note inputs for note with id {0}")]
207 WrongNumInputs(NoteId, usize),
208 #[error("note input representing block with value {1} for note with id {0}")]
209 BlockNumberError(NoteId, u64),
210}