1use alloc::collections::BTreeMap;
2
3use miden_protocol::account::AccountId;
4use miden_protocol::block::{BlockHeader, BlockNumber};
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 pub(crate) fn mark_erased_note_as_consumed(
351 &mut self,
352 note_id: NoteId,
353 block_num: BlockNumber,
354 ) -> Result<(), ClientError> {
355 if let Some(output_note) = self.get_output_note_by_id(note_id)
356 && !output_note.is_consumed()
357 && !output_note.is_committed()
358 && let Some(nullifier) = output_note.nullifier()
359 {
360 output_note.nullifier_received(nullifier, block_num)?;
361 }
362
363 if let Some(input_note_update) = self.input_notes.get_mut(¬e_id)
365 && !input_note_update.inner().is_consumed()
366 {
367 let nullifier = input_note_update.inner().nullifier();
368 let consumer = input_note_update.inner().metadata().and_then(|m| {
369 miden_standards::note::NetworkAccountTarget::try_from(m.attachment())
370 .ok()
371 .map(|t| t.target_id())
372 });
373 input_note_update
374 .inner_mut()
375 .consumed_externally(nullifier, block_num, consumer)?;
376 input_note_update.inner_mut().set_consumed_tx_order(Some(0));
377 }
378
379 Ok(())
380 }
381
382 fn try_commit_output_note(
385 &mut self,
386 note_id: NoteId,
387 inclusion_proof: NoteInclusionProof,
388 ) -> Result<(), ClientError> {
389 if let Some(output_note) = self.get_output_note_by_id(note_id) {
390 output_note.inclusion_proof_received(inclusion_proof)?;
391 }
392 Ok(())
393 }
394
395 pub(crate) fn apply_nullifiers_state_transitions<'a>(
405 &mut self,
406 nullifier_update: &NullifierUpdate,
407 mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
408 external_consumer_account: Option<AccountId>,
409 ) -> Result<(), ClientError> {
410 let order = self.get_nullifier_order(nullifier_update.nullifier);
411
412 if let Some(input_note_update) =
413 self.get_input_note_update_by_nullifier(nullifier_update.nullifier)
414 {
415 if let Some(consumer_transaction) = committed_transactions
416 .find(|t| input_note_update.inner().consumer_transaction_id() == Some(&t.id))
417 {
418 if let TransactionStatus::Committed { block_number, .. } =
420 consumer_transaction.status
421 {
422 input_note_update
423 .inner_mut()
424 .transaction_committed(consumer_transaction.id, block_number)?;
425 }
426 } else {
427 input_note_update.inner_mut().consumed_externally(
430 nullifier_update.nullifier,
431 nullifier_update.block_num,
432 external_consumer_account,
433 )?;
434 }
435 input_note_update.inner_mut().set_consumed_tx_order(order);
436 }
437
438 if let Some(output_note_record) =
439 self.get_output_note_by_nullifier(nullifier_update.nullifier)
440 {
441 output_note_record
442 .nullifier_received(nullifier_update.nullifier, nullifier_update.block_num)?;
443 }
444
445 Ok(())
446 }
447
448 fn get_nullifier_order(&self, nullifier: Nullifier) -> Option<u32> {
454 self.nullifier_order.get(&nullifier).copied()
455 }
456
457 fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
459 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
460 }
461
462 fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
464 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
465 }
466
467 fn get_input_note_update_by_nullifier(
470 &mut self,
471 nullifier: Nullifier,
472 ) -> Option<&mut InputNoteUpdate> {
473 let note_id = self.input_notes_by_nullifier.get(&nullifier).copied()?;
474 self.input_notes.get_mut(¬e_id)
475 }
476
477 fn get_output_note_by_nullifier(
480 &mut self,
481 nullifier: Nullifier,
482 ) -> Option<&mut OutputNoteRecord> {
483 let note_id = self.output_notes_by_nullifier.get(&nullifier).copied()?;
484 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
485 }
486
487 fn insert_input_note(&mut self, note: InputNoteRecord, update_type: NoteUpdateType) {
489 let note_id = note.id();
490 let nullifier = note.nullifier();
491 self.input_notes_by_nullifier.insert(nullifier, note_id);
492 let update = match update_type {
493 NoteUpdateType::None => InputNoteUpdate::new_none(note),
494 NoteUpdateType::Insert => InputNoteUpdate::new_insert(note),
495 NoteUpdateType::Update => InputNoteUpdate::new_update(note),
496 };
497 self.input_notes.insert(note_id, update);
498 }
499
500 fn insert_output_note(&mut self, note: OutputNoteRecord, update_type: NoteUpdateType) {
502 let note_id = note.id();
503 if let Some(nullifier) = note.nullifier() {
504 self.output_notes_by_nullifier.insert(nullifier, note_id);
505 }
506 let update = match update_type {
507 NoteUpdateType::None => OutputNoteUpdate::new_none(note),
508 NoteUpdateType::Insert => OutputNoteUpdate::new_insert(note),
509 NoteUpdateType::Update => OutputNoteUpdate::new_update(note),
510 };
511 self.output_notes.insert(note_id, update);
512 }
513}