1use alloc::collections::BTreeMap;
2
3use miden_protocol::account::AccountId;
4use miden_protocol::block::{BlockHeader, BlockNumber};
5use miden_protocol::note::{
6 Note,
7 NoteAttachments,
8 NoteDetailsCommitment,
9 NoteHeader,
10 NoteId,
11 NoteInclusionProof,
12 NoteMetadata,
13 Nullifier,
14};
15use miden_standards::note::NetworkAccountTarget;
16use miden_tx::utils::serde::{
17 ByteReader,
18 ByteWriter,
19 Deserializable,
20 DeserializationError,
21 Serializable,
22};
23
24use crate::ClientError;
25use crate::rpc::domain::note::CommittedNote;
26use crate::store::{InputNoteRecord, OutputNoteRecord};
27use crate::transaction::{TransactionRecord, TransactionStatus};
28
29pub struct NoteConsumption {
34 pub nullifier: Nullifier,
36 pub block_num: BlockNumber,
38 pub external_consumer: Option<AccountId>,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50#[repr(u8)]
51pub enum NoteUpdateType {
52 None = 0,
54 Insert = 1,
56 Update = 2,
58 InsertCommitted = 3,
63}
64
65impl TryFrom<u8> for NoteUpdateType {
66 type Error = u8;
67
68 fn try_from(value: u8) -> Result<Self, Self::Error> {
69 match value {
70 0 => Ok(NoteUpdateType::None),
71 1 => Ok(NoteUpdateType::Insert),
72 2 => Ok(NoteUpdateType::Update),
73 3 => Ok(NoteUpdateType::InsertCommitted),
74 other => Err(other),
75 }
76 }
77}
78
79#[derive(Clone, Debug, PartialEq)]
81pub struct InputNoteUpdate {
82 note: InputNoteRecord,
84 update_type: NoteUpdateType,
86}
87
88impl InputNoteUpdate {
89 fn new_none(note: InputNoteRecord) -> Self {
91 Self { note, update_type: NoteUpdateType::None }
92 }
93
94 fn new_insert(note: InputNoteRecord) -> Self {
96 Self {
97 note,
98 update_type: NoteUpdateType::Insert,
99 }
100 }
101
102 fn new_update(note: InputNoteRecord) -> Self {
104 Self {
105 note,
106 update_type: NoteUpdateType::Update,
107 }
108 }
109
110 fn new_insert_committed(note: InputNoteRecord) -> Self {
113 Self {
114 note,
115 update_type: NoteUpdateType::InsertCommitted,
116 }
117 }
118
119 pub fn inner(&self) -> &InputNoteRecord {
121 &self.note
122 }
123
124 fn inner_mut(&mut self) -> &mut InputNoteRecord {
127 self.update_type = match self.update_type {
128 NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
129 NoteUpdateType::Insert => NoteUpdateType::Insert,
130 NoteUpdateType::InsertCommitted => NoteUpdateType::InsertCommitted,
131 };
132
133 &mut self.note
134 }
135
136 pub fn update_type(&self) -> &NoteUpdateType {
138 &self.update_type
139 }
140
141 pub fn id(&self) -> Option<NoteId> {
144 self.note.id()
145 }
146
147 pub fn consumed_tx_order(&self) -> Option<u32> {
151 self.note.state().consumed_tx_order()
152 }
153}
154
155#[derive(Clone, Debug, PartialEq)]
157pub struct OutputNoteUpdate {
158 note: OutputNoteRecord,
160 update_type: NoteUpdateType,
162}
163
164impl OutputNoteUpdate {
165 fn new_none(note: OutputNoteRecord) -> Self {
167 Self { note, update_type: NoteUpdateType::None }
168 }
169
170 fn new_insert(note: OutputNoteRecord) -> Self {
172 Self {
173 note,
174 update_type: NoteUpdateType::Insert,
175 }
176 }
177
178 fn new_update(note: OutputNoteRecord) -> Self {
180 Self {
181 note,
182 update_type: NoteUpdateType::Update,
183 }
184 }
185
186 pub fn inner(&self) -> &OutputNoteRecord {
188 &self.note
189 }
190
191 fn inner_mut(&mut self) -> &mut OutputNoteRecord {
194 self.update_type = match self.update_type {
195 NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
196 NoteUpdateType::Insert | NoteUpdateType::InsertCommitted => NoteUpdateType::Insert,
199 };
200
201 &mut self.note
202 }
203
204 pub fn update_type(&self) -> &NoteUpdateType {
206 &self.update_type
207 }
208
209 pub fn id(&self) -> NoteId {
211 self.note.id()
212 }
213}
214
215#[derive(Clone, Debug, Default, PartialEq)]
224pub struct NoteUpdateTracker {
225 input_notes: BTreeMap<NoteId, InputNoteUpdate>,
231 expected_input_notes: BTreeMap<NoteDetailsCommitment, InputNoteUpdate>,
234 output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
236 input_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
238 output_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
240 nullifier_order: BTreeMap<Nullifier, u32>,
244}
245
246impl NoteUpdateTracker {
247 pub fn new(
249 input_notes: impl IntoIterator<Item = InputNoteRecord>,
250 output_notes: impl IntoIterator<Item = OutputNoteRecord>,
251 ) -> Self {
252 let mut tracker = Self::default();
253 for note in input_notes {
254 tracker.insert_input_note(note, NoteUpdateType::None);
255 }
256 for note in output_notes {
257 tracker.insert_output_note(note, NoteUpdateType::None);
258 }
259
260 tracker
261 }
262
263 pub fn for_transaction_updates(
271 new_input_notes: impl IntoIterator<Item = InputNoteRecord>,
272 updated_input_notes: impl IntoIterator<Item = InputNoteRecord>,
273 new_output_notes: impl IntoIterator<Item = OutputNoteRecord>,
274 ) -> Self {
275 let mut tracker = Self::default();
276
277 for note in new_input_notes {
278 tracker.insert_input_note(note, NoteUpdateType::Insert);
279 }
280
281 for note in updated_input_notes {
282 tracker.insert_input_note(note, NoteUpdateType::Update);
283 }
284
285 for note in new_output_notes {
286 tracker.insert_output_note(note, NoteUpdateType::Insert);
287 }
288
289 tracker
290 }
291
292 pub fn updated_input_notes(&self) -> impl Iterator<Item = &InputNoteUpdate> {
306 self.input_notes
307 .values()
308 .chain(self.expected_input_notes.values())
309 .filter(|note| {
310 matches!(
311 note.update_type,
312 NoteUpdateType::Insert
313 | NoteUpdateType::Update
314 | NoteUpdateType::InsertCommitted
315 )
316 })
317 }
318
319 pub fn consumed_input_note_ids(&self) -> impl Iterator<Item = NoteId> + '_ {
323 self.input_notes
324 .iter()
325 .filter(|(_, update)| {
326 matches!(
327 update.update_type,
328 NoteUpdateType::Insert
329 | NoteUpdateType::Update
330 | NoteUpdateType::InsertCommitted
331 ) && update.inner().is_consumed()
332 })
333 .map(|(note_id, _)| *note_id)
334 }
335
336 pub fn updated_output_notes(&self) -> impl Iterator<Item = &OutputNoteUpdate> {
342 self.output_notes.values().filter(|note| {
343 matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
344 })
345 }
346
347 pub fn is_empty(&self) -> bool {
349 self.input_notes.is_empty()
350 && self.output_notes.is_empty()
351 && self.expected_input_notes.is_empty()
352 }
353
354 pub fn unspent_nullifiers(&self) -> impl Iterator<Item = Nullifier> {
356 let input_note_unspent_nullifiers = self
357 .input_notes
358 .values()
359 .filter(|note| !note.inner().is_consumed())
360 .filter_map(|note| note.inner().nullifier());
361
362 let output_note_unspent_nullifiers = self
363 .output_notes
364 .values()
365 .filter(|note| !note.inner().is_consumed())
366 .filter_map(|note| note.inner().nullifier());
367
368 input_note_unspent_nullifiers.chain(output_note_unspent_nullifiers)
369 }
370
371 pub fn extend_nullifiers(&mut self, nullifiers: impl IntoIterator<Item = Nullifier>) {
376 for nullifier in nullifiers {
377 let next_pos =
378 u32::try_from(self.nullifier_order.len()).expect("nullifier count exceeds u32");
379 self.nullifier_order.entry(nullifier).or_insert(next_pos);
380 }
381 }
382
383 pub(crate) fn apply_new_public_note(
390 &mut self,
391 mut public_note_data: InputNoteRecord,
392 block_header: &BlockHeader,
393 ) -> Result<(), ClientError> {
394 public_note_data.block_header_received(block_header)?;
395 self.insert_input_note(public_note_data, NoteUpdateType::Insert);
396
397 Ok(())
398 }
399
400 pub(crate) fn apply_committed_note_state_transitions(
403 &mut self,
404 committed_note: &CommittedNote,
405 block_header: &BlockHeader,
406 attachments: Option<&NoteAttachments>,
407 ) -> Result<bool, ClientError> {
408 let inclusion_proof = committed_note.inclusion_proof().clone();
409 let metadata = *committed_note.metadata();
410 let note_id = *committed_note.note_id();
411
412 let is_tracked_as_input_note =
413 if let Some(input_note_record) = self.get_input_note_by_id(note_id) {
414 input_note_record.inclusion_proof_received(inclusion_proof.clone(), metadata)?;
415 input_note_record.block_header_received(block_header)?;
416 if let Some(attachments) = attachments {
417 input_note_record.set_attachments(attachments.clone());
418 }
419
420 true
421 } else if let Some(commitment) = self.expected_note_matching(note_id, &metadata) {
422 let mut update = self
425 .expected_input_notes
426 .remove(&commitment)
427 .expect("commitment was just matched against the expected notes");
428 let record = &mut update.note;
429 record.inclusion_proof_received(inclusion_proof.clone(), metadata)?;
430 record.block_header_received(block_header)?;
431 if let Some(attachments) = attachments {
432 record.set_attachments(attachments.clone());
433 }
434
435 let nullifier = record.nullifier().expect("note with an id has metadata");
439 self.input_notes_by_nullifier.insert(nullifier, note_id);
440 self.input_notes
441 .insert(note_id, InputNoteUpdate::new_insert_committed(update.note));
442
443 true
444 } else {
445 false
446 };
447
448 self.try_commit_output_note(note_id, inclusion_proof)?;
449
450 Ok(is_tracked_as_input_note)
451 }
452
453 pub(crate) fn apply_output_note_inclusion_proofs(
458 &mut self,
459 committed_notes: &[CommittedNote],
460 ) -> Result<(), ClientError> {
461 for committed_note in committed_notes {
462 self.try_commit_output_note(
463 *committed_note.note_id(),
464 committed_note.inclusion_proof().clone(),
465 )?;
466 }
467 Ok(())
468 }
469
470 pub(crate) fn mark_erased_note_as_consumed(
480 &mut self,
481 note_header: &NoteHeader,
482 block_num: BlockNumber,
483 ) -> Result<(), ClientError> {
484 let note_id = note_header.id();
485
486 if let Some(output_note) = self.get_output_note_by_id(note_id)
487 && !output_note.is_consumed()
488 && !output_note.is_committed()
489 && let Some(nullifier) = output_note.nullifier()
490 {
491 output_note.nullifier_received(nullifier, block_num)?;
492 }
493
494 if let Some(input_note_update) = self.input_notes.get_mut(¬e_id)
495 && !input_note_update.inner().is_consumed()
496 && let Some(nullifier) = input_note_update.inner().nullifier()
497 {
498 let consumer_account =
499 NetworkAccountTarget::try_from(input_note_update.inner().attachments())
500 .ok()
501 .map(|target| target.target_id());
502 input_note_update.inner_mut().consumed_externally(
503 nullifier,
504 block_num,
505 consumer_account,
506 )?;
507 input_note_update.inner_mut().set_consumed_tx_order(Some(0));
508 }
509
510 Ok(())
511 }
512
513 fn try_insert_consumed_input_from_output(
520 &mut self,
521 note_id: NoteId,
522 consumer: AccountId,
523 block_num: BlockNumber,
524 consumed_tx_order: Option<u32>,
525 ) -> Result<(), ClientError> {
526 if self.input_notes.contains_key(¬e_id) {
527 return Ok(());
528 }
529 let Some(output_note) = self.output_notes.get(¬e_id) else {
530 return Ok(());
531 };
532 let Ok(note) = Note::try_from(output_note.inner().clone()) else {
533 return Ok(());
534 };
535
536 let mut input_record = InputNoteRecord::from(note);
537 let nullifier =
538 input_record.nullifier().expect("record built from a full note has metadata");
539 input_record.consumed_externally(nullifier, block_num, Some(consumer))?;
540 input_record.set_consumed_tx_order(consumed_tx_order);
541 self.insert_input_note(input_record, NoteUpdateType::Insert);
542 Ok(())
543 }
544
545 fn try_commit_output_note(
548 &mut self,
549 note_id: NoteId,
550 inclusion_proof: NoteInclusionProof,
551 ) -> Result<(), ClientError> {
552 if let Some(output_note) = self.get_output_note_by_id(note_id) {
553 output_note.inclusion_proof_received(inclusion_proof)?;
554 }
555 Ok(())
556 }
557
558 pub(crate) fn apply_note_consumption<'a>(
572 &mut self,
573 consumption: &NoteConsumption,
574 mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
575 ) -> Result<(), ClientError> {
576 let nullifier = consumption.nullifier;
577 let block_num = consumption.block_num;
578 let external_consumer = consumption.external_consumer;
579 let order = self.get_nullifier_order(nullifier);
580 let input_present = self.input_notes_by_nullifier.contains_key(&nullifier);
581
582 if let Some(input_note_update) = self.get_input_note_update_by_nullifier(nullifier) {
583 if let Some(consumer_transaction) = committed_transactions
584 .find(|t| input_note_update.inner().consumer_transaction_id() == Some(&t.id))
585 {
586 if let TransactionStatus::Committed { block_number, .. } =
588 consumer_transaction.status
589 {
590 input_note_update
591 .inner_mut()
592 .transaction_committed(consumer_transaction.id, block_number)?;
593 }
594 } else {
595 input_note_update.inner_mut().consumed_externally(
598 nullifier,
599 block_num,
600 external_consumer,
601 )?;
602 }
603 input_note_update.inner_mut().set_consumed_tx_order(order);
604 }
605
606 if let Some(output_note_record) = self.get_output_note_by_nullifier(nullifier) {
607 output_note_record.nullifier_received(nullifier, block_num)?;
608 }
609
610 if !input_present
611 && let Some(consumer) = external_consumer
612 && let Some(note_id) = self.output_notes_by_nullifier.get(&nullifier).copied()
613 {
614 self.try_insert_consumed_input_from_output(note_id, consumer, block_num, order)?;
615 }
616
617 Ok(())
618 }
619
620 fn get_nullifier_order(&self, nullifier: Nullifier) -> Option<u32> {
626 self.nullifier_order.get(&nullifier).copied()
627 }
628
629 fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
631 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
632 }
633
634 fn expected_note_matching(
637 &self,
638 note_id: NoteId,
639 metadata: &NoteMetadata,
640 ) -> Option<NoteDetailsCommitment> {
641 self.expected_input_notes
642 .keys()
643 .copied()
644 .find(|commitment| NoteId::new(*commitment, metadata) == note_id)
645 }
646
647 fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
649 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
650 }
651
652 fn get_input_note_update_by_nullifier(
655 &mut self,
656 nullifier: Nullifier,
657 ) -> Option<&mut InputNoteUpdate> {
658 let note_id = self.input_notes_by_nullifier.get(&nullifier).copied()?;
659 self.input_notes.get_mut(¬e_id)
660 }
661
662 fn get_output_note_by_nullifier(
665 &mut self,
666 nullifier: Nullifier,
667 ) -> Option<&mut OutputNoteRecord> {
668 let note_id = self.output_notes_by_nullifier.get(&nullifier).copied()?;
669 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
670 }
671
672 fn insert_input_note(&mut self, note: InputNoteRecord, update_type: NoteUpdateType) {
674 let update = match update_type {
675 NoteUpdateType::None => InputNoteUpdate::new_none(note),
676 NoteUpdateType::Insert => InputNoteUpdate::new_insert(note),
677 NoteUpdateType::Update => InputNoteUpdate::new_update(note),
678 NoteUpdateType::InsertCommitted => InputNoteUpdate::new_insert_committed(note),
679 };
680
681 if let Some(note_id) = update.inner().id() {
682 self.expected_input_notes.remove(&update.inner().details_commitment());
684 let nullifier = update.inner().nullifier().expect("note with an id has metadata");
685 self.input_notes_by_nullifier.insert(nullifier, note_id);
686 self.input_notes.insert(note_id, update);
687 } else {
688 let commitment = update.inner().details_commitment();
691 self.expected_input_notes.insert(commitment, update);
692 }
693 }
694
695 fn insert_output_note(&mut self, note: OutputNoteRecord, update_type: NoteUpdateType) {
697 let note_id = note.id();
698 if let Some(nullifier) = note.nullifier() {
699 self.output_notes_by_nullifier.insert(nullifier, note_id);
700 }
701 let update = match update_type {
702 NoteUpdateType::None => OutputNoteUpdate::new_none(note),
703 NoteUpdateType::Update => OutputNoteUpdate::new_update(note),
704 NoteUpdateType::Insert | NoteUpdateType::InsertCommitted => {
707 OutputNoteUpdate::new_insert(note)
708 },
709 };
710 self.output_notes.insert(note_id, update);
711 }
712}
713
714impl Serializable for NoteUpdateType {
718 fn write_into<W: ByteWriter>(&self, target: &mut W) {
719 target.write_u8(*self as u8);
720 }
721}
722
723impl Deserializable for NoteUpdateType {
724 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
725 NoteUpdateType::try_from(source.read_u8()?).map_err(|val| {
726 DeserializationError::InvalidValue(format!("invalid note update type: {val}"))
727 })
728 }
729}
730
731impl Serializable for InputNoteUpdate {
732 fn write_into<W: ByteWriter>(&self, target: &mut W) {
733 self.note.write_into(target);
734 self.update_type.write_into(target);
735 }
736}
737
738impl Deserializable for InputNoteUpdate {
739 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
740 let note = InputNoteRecord::read_from(source)?;
741 let update_type = NoteUpdateType::read_from(source)?;
742 Ok(Self { note, update_type })
743 }
744}
745
746impl Serializable for OutputNoteUpdate {
747 fn write_into<W: ByteWriter>(&self, target: &mut W) {
748 self.note.write_into(target);
749 self.update_type.write_into(target);
750 }
751}
752
753impl Deserializable for OutputNoteUpdate {
754 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
755 let note = OutputNoteRecord::read_from(source)?;
756 let update_type = NoteUpdateType::read_from(source)?;
757 Ok(Self { note, update_type })
758 }
759}
760
761impl Serializable for NoteUpdateTracker {
762 fn write_into<W: ByteWriter>(&self, target: &mut W) {
763 self.input_notes.write_into(target);
766 self.output_notes.write_into(target);
767 self.expected_input_notes.write_into(target);
768 self.nullifier_order.write_into(target);
769 }
770}
771
772impl Deserializable for NoteUpdateTracker {
773 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
774 let input_notes = BTreeMap::<NoteId, InputNoteUpdate>::read_from(source)?;
775 let output_notes = BTreeMap::<NoteId, OutputNoteUpdate>::read_from(source)?;
776 let expected_input_notes =
777 BTreeMap::<NoteDetailsCommitment, InputNoteUpdate>::read_from(source)?;
778 let nullifier_order = BTreeMap::<Nullifier, u32>::read_from(source)?;
779
780 let input_notes_by_nullifier = input_notes
781 .iter()
782 .map(|(note_id, update)| {
783 (update.inner().nullifier().expect("note with an id has metadata"), *note_id)
784 })
785 .collect();
786 let output_notes_by_nullifier = output_notes
787 .iter()
788 .filter_map(|(note_id, update)| {
789 update.inner().nullifier().map(|nullifier| (nullifier, *note_id))
790 })
791 .collect();
792
793 Ok(Self {
794 input_notes,
795 expected_input_notes,
796 output_notes,
797 input_notes_by_nullifier,
798 output_notes_by_nullifier,
799 nullifier_order,
800 })
801 }
802}