1use alloc::collections::BTreeMap;
2
3use miden_protocol::account::AccountId;
4use miden_protocol::block::BlockHeader;
5use miden_protocol::note::{NoteId, NoteInclusionProof, Nullifier};
6
7use crate::ClientError;
8use crate::rpc::RpcError;
9use crate::rpc::domain::note::CommittedNote;
10use crate::rpc::domain::nullifier::NullifierUpdate;
11use crate::store::{InputNoteRecord, OutputNoteRecord};
12use crate::transaction::{TransactionRecord, TransactionStatus};
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum NoteUpdateType {
21 None,
23 Insert,
25 Update,
27}
28
29#[derive(Clone, Debug)]
31pub struct InputNoteUpdate {
32 note: InputNoteRecord,
34 update_type: NoteUpdateType,
36}
37
38impl InputNoteUpdate {
39 fn new_none(note: InputNoteRecord) -> Self {
41 Self { note, update_type: NoteUpdateType::None }
42 }
43
44 fn new_insert(note: InputNoteRecord) -> Self {
46 Self {
47 note,
48 update_type: NoteUpdateType::Insert,
49 }
50 }
51
52 fn new_update(note: InputNoteRecord) -> Self {
54 Self {
55 note,
56 update_type: NoteUpdateType::Update,
57 }
58 }
59
60 pub fn inner(&self) -> &InputNoteRecord {
62 &self.note
63 }
64
65 fn inner_mut(&mut self) -> &mut InputNoteRecord {
68 self.update_type = match self.update_type {
69 NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
70 NoteUpdateType::Insert => NoteUpdateType::Insert,
71 };
72
73 &mut self.note
74 }
75
76 pub fn update_type(&self) -> &NoteUpdateType {
78 &self.update_type
79 }
80
81 pub fn id(&self) -> NoteId {
83 self.note.id()
84 }
85
86 pub fn consumed_tx_order(&self) -> Option<u32> {
90 self.note.state().consumed_tx_order()
91 }
92}
93
94#[derive(Clone, Debug)]
96pub struct OutputNoteUpdate {
97 note: OutputNoteRecord,
99 update_type: NoteUpdateType,
101}
102
103impl OutputNoteUpdate {
104 fn new_none(note: OutputNoteRecord) -> Self {
106 Self { note, update_type: NoteUpdateType::None }
107 }
108
109 fn new_insert(note: OutputNoteRecord) -> Self {
111 Self {
112 note,
113 update_type: NoteUpdateType::Insert,
114 }
115 }
116
117 fn new_update(note: OutputNoteRecord) -> Self {
119 Self {
120 note,
121 update_type: NoteUpdateType::Update,
122 }
123 }
124
125 pub fn inner(&self) -> &OutputNoteRecord {
127 &self.note
128 }
129
130 fn inner_mut(&mut self) -> &mut OutputNoteRecord {
133 self.update_type = match self.update_type {
134 NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
135 NoteUpdateType::Insert => NoteUpdateType::Insert,
136 };
137
138 &mut self.note
139 }
140
141 pub fn update_type(&self) -> &NoteUpdateType {
143 &self.update_type
144 }
145
146 pub fn id(&self) -> NoteId {
148 self.note.id()
149 }
150}
151
152#[derive(Clone, Debug, Default)]
161pub struct NoteUpdateTracker {
162 input_notes: BTreeMap<NoteId, InputNoteUpdate>,
164 output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
166 input_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
168 output_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
170 nullifier_order: BTreeMap<Nullifier, u32>,
174}
175
176impl NoteUpdateTracker {
177 pub fn new(
179 input_notes: impl IntoIterator<Item = InputNoteRecord>,
180 output_notes: impl IntoIterator<Item = OutputNoteRecord>,
181 ) -> Self {
182 let mut tracker = Self::default();
183 for note in input_notes {
184 tracker.insert_input_note(note, NoteUpdateType::None);
185 }
186 for note in output_notes {
187 tracker.insert_output_note(note, NoteUpdateType::None);
188 }
189
190 tracker
191 }
192
193 pub fn for_transaction_updates(
201 new_input_notes: impl IntoIterator<Item = InputNoteRecord>,
202 updated_input_notes: impl IntoIterator<Item = InputNoteRecord>,
203 new_output_notes: impl IntoIterator<Item = OutputNoteRecord>,
204 ) -> Self {
205 let mut tracker = Self::default();
206
207 for note in new_input_notes {
208 tracker.insert_input_note(note, NoteUpdateType::Insert);
209 }
210
211 for note in updated_input_notes {
212 tracker.insert_input_note(note, NoteUpdateType::Update);
213 }
214
215 for note in new_output_notes {
216 tracker.insert_output_note(note, NoteUpdateType::Insert);
217 }
218
219 tracker
220 }
221
222 pub fn updated_input_notes(&self) -> impl Iterator<Item = &InputNoteUpdate> {
231 self.input_notes.values().filter(|note| {
232 matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
233 })
234 }
235
236 pub fn updated_output_notes(&self) -> impl Iterator<Item = &OutputNoteUpdate> {
242 self.output_notes.values().filter(|note| {
243 matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
244 })
245 }
246
247 pub fn is_empty(&self) -> bool {
249 self.input_notes.is_empty() && self.output_notes.is_empty()
250 }
251
252 pub fn unspent_nullifiers(&self) -> impl Iterator<Item = Nullifier> {
254 let input_note_unspent_nullifiers = self
255 .input_notes
256 .values()
257 .filter(|note| !note.inner().is_consumed())
258 .map(|note| note.inner().nullifier());
259
260 let output_note_unspent_nullifiers = self
261 .output_notes
262 .values()
263 .filter(|note| !note.inner().is_consumed())
264 .filter_map(|note| note.inner().nullifier());
265
266 input_note_unspent_nullifiers.chain(output_note_unspent_nullifiers)
267 }
268
269 pub fn extend_nullifiers(&mut self, nullifiers: impl IntoIterator<Item = Nullifier>) {
274 for nullifier in nullifiers {
275 let next_pos =
276 u32::try_from(self.nullifier_order.len()).expect("nullifier count exceeds u32");
277 self.nullifier_order.entry(nullifier).or_insert(next_pos);
278 }
279 }
280
281 pub(crate) fn apply_new_public_note(
288 &mut self,
289 mut public_note_data: InputNoteRecord,
290 block_header: &BlockHeader,
291 ) -> Result<(), ClientError> {
292 public_note_data.block_header_received(block_header)?;
293 self.insert_input_note(public_note_data, NoteUpdateType::Insert);
294
295 Ok(())
296 }
297
298 pub(crate) fn apply_committed_note_state_transitions(
301 &mut self,
302 committed_note: &CommittedNote,
303 block_header: &BlockHeader,
304 ) -> Result<bool, ClientError> {
305 let inclusion_proof = committed_note.inclusion_proof().clone();
306
307 let is_tracked_as_input_note =
308 if let Some(input_note_record) = self.get_input_note_by_id(*committed_note.note_id()) {
309 let metadata = committed_note.metadata().cloned().ok_or_else(|| {
310 ClientError::RpcError(RpcError::ExpectedDataMissing(format!(
311 "full metadata for committed note {}",
312 committed_note.note_id()
313 )))
314 })?;
315 input_note_record.inclusion_proof_received(inclusion_proof.clone(), metadata)?;
316 input_note_record.block_header_received(block_header)?;
317
318 true
319 } else {
320 false
321 };
322
323 self.try_commit_output_note(*committed_note.note_id(), inclusion_proof)?;
324
325 Ok(is_tracked_as_input_note)
326 }
327
328 pub(crate) fn apply_output_note_inclusion_proofs(
333 &mut self,
334 committed_notes: &[CommittedNote],
335 ) -> Result<(), ClientError> {
336 for committed_note in committed_notes {
337 self.try_commit_output_note(
338 *committed_note.note_id(),
339 committed_note.inclusion_proof().clone(),
340 )?;
341 }
342 Ok(())
343 }
344
345 fn try_commit_output_note(
348 &mut self,
349 note_id: NoteId,
350 inclusion_proof: NoteInclusionProof,
351 ) -> Result<(), ClientError> {
352 if let Some(output_note) = self.get_output_note_by_id(note_id) {
353 output_note.inclusion_proof_received(inclusion_proof)?;
354 }
355 Ok(())
356 }
357
358 pub(crate) fn apply_nullifiers_state_transitions<'a>(
368 &mut self,
369 nullifier_update: &NullifierUpdate,
370 mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
371 external_consumer_account: Option<AccountId>,
372 ) -> Result<(), ClientError> {
373 let order = self.get_nullifier_order(nullifier_update.nullifier);
374
375 if let Some(input_note_update) =
376 self.get_input_note_update_by_nullifier(nullifier_update.nullifier)
377 {
378 if let Some(consumer_transaction) = committed_transactions
379 .find(|t| input_note_update.inner().consumer_transaction_id() == Some(&t.id))
380 {
381 if let TransactionStatus::Committed { block_number, .. } =
383 consumer_transaction.status
384 {
385 input_note_update
386 .inner_mut()
387 .transaction_committed(consumer_transaction.id, block_number)?;
388 }
389 } else {
390 input_note_update.inner_mut().consumed_externally(
393 nullifier_update.nullifier,
394 nullifier_update.block_num,
395 external_consumer_account,
396 )?;
397 }
398 input_note_update.inner_mut().set_consumed_tx_order(order);
399 }
400
401 if let Some(output_note_record) =
402 self.get_output_note_by_nullifier(nullifier_update.nullifier)
403 {
404 output_note_record
405 .nullifier_received(nullifier_update.nullifier, nullifier_update.block_num)?;
406 }
407
408 Ok(())
409 }
410
411 fn get_nullifier_order(&self, nullifier: Nullifier) -> Option<u32> {
417 self.nullifier_order.get(&nullifier).copied()
418 }
419
420 fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
422 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
423 }
424
425 fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
427 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
428 }
429
430 fn get_input_note_update_by_nullifier(
433 &mut self,
434 nullifier: Nullifier,
435 ) -> Option<&mut InputNoteUpdate> {
436 let note_id = self.input_notes_by_nullifier.get(&nullifier).copied()?;
437 self.input_notes.get_mut(¬e_id)
438 }
439
440 fn get_output_note_by_nullifier(
443 &mut self,
444 nullifier: Nullifier,
445 ) -> Option<&mut OutputNoteRecord> {
446 let note_id = self.output_notes_by_nullifier.get(&nullifier).copied()?;
447 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
448 }
449
450 fn insert_input_note(&mut self, note: InputNoteRecord, update_type: NoteUpdateType) {
452 let note_id = note.id();
453 let nullifier = note.nullifier();
454 self.input_notes_by_nullifier.insert(nullifier, note_id);
455 let update = match update_type {
456 NoteUpdateType::None => InputNoteUpdate::new_none(note),
457 NoteUpdateType::Insert => InputNoteUpdate::new_insert(note),
458 NoteUpdateType::Update => InputNoteUpdate::new_update(note),
459 };
460 self.input_notes.insert(note_id, update);
461 }
462
463 fn insert_output_note(&mut self, note: OutputNoteRecord, update_type: NoteUpdateType) {
465 let note_id = note.id();
466 if let Some(nullifier) = note.nullifier() {
467 self.output_notes_by_nullifier.insert(nullifier, note_id);
468 }
469 let update = match update_type {
470 NoteUpdateType::None => OutputNoteUpdate::new_none(note),
471 NoteUpdateType::Insert => OutputNoteUpdate::new_insert(note),
472 NoteUpdateType::Update => OutputNoteUpdate::new_update(note),
473 };
474 self.output_notes.insert(note_id, update);
475 }
476}