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 NoteUpdateType {
66 pub fn is_modified(self) -> bool {
70 matches!(self, Self::Insert | Self::Update | Self::InsertCommitted)
71 }
72}
73
74impl TryFrom<u8> for NoteUpdateType {
75 type Error = u8;
76
77 fn try_from(value: u8) -> Result<Self, Self::Error> {
78 match value {
79 0 => Ok(NoteUpdateType::None),
80 1 => Ok(NoteUpdateType::Insert),
81 2 => Ok(NoteUpdateType::Update),
82 3 => Ok(NoteUpdateType::InsertCommitted),
83 other => Err(other),
84 }
85 }
86}
87
88#[derive(Clone, Debug, PartialEq)]
90pub struct InputNoteUpdate {
91 note: InputNoteRecord,
93 update_type: NoteUpdateType,
95}
96
97impl InputNoteUpdate {
98 fn new_none(note: InputNoteRecord) -> Self {
100 Self { note, update_type: NoteUpdateType::None }
101 }
102
103 fn new_insert(note: InputNoteRecord) -> Self {
105 Self {
106 note,
107 update_type: NoteUpdateType::Insert,
108 }
109 }
110
111 fn new_update(note: InputNoteRecord) -> Self {
113 Self {
114 note,
115 update_type: NoteUpdateType::Update,
116 }
117 }
118
119 fn new_insert_committed(note: InputNoteRecord) -> Self {
122 Self {
123 note,
124 update_type: NoteUpdateType::InsertCommitted,
125 }
126 }
127
128 pub fn inner(&self) -> &InputNoteRecord {
130 &self.note
131 }
132
133 fn inner_mut(&mut self) -> &mut InputNoteRecord {
136 self.update_type = match self.update_type {
137 NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
138 NoteUpdateType::Insert => NoteUpdateType::Insert,
139 NoteUpdateType::InsertCommitted => NoteUpdateType::InsertCommitted,
140 };
141
142 &mut self.note
143 }
144
145 pub fn update_type(&self) -> &NoteUpdateType {
147 &self.update_type
148 }
149
150 pub fn id(&self) -> Option<NoteId> {
153 self.note.id()
154 }
155
156 pub fn consumed_tx_order(&self) -> Option<u32> {
160 self.note.state().consumed_tx_order()
161 }
162}
163
164#[derive(Clone, Debug, PartialEq)]
166pub struct OutputNoteUpdate {
167 note: OutputNoteRecord,
169 update_type: NoteUpdateType,
171}
172
173impl OutputNoteUpdate {
174 fn new_none(note: OutputNoteRecord) -> Self {
176 Self { note, update_type: NoteUpdateType::None }
177 }
178
179 fn new_insert(note: OutputNoteRecord) -> Self {
181 Self {
182 note,
183 update_type: NoteUpdateType::Insert,
184 }
185 }
186
187 fn new_update(note: OutputNoteRecord) -> Self {
189 Self {
190 note,
191 update_type: NoteUpdateType::Update,
192 }
193 }
194
195 pub fn inner(&self) -> &OutputNoteRecord {
197 &self.note
198 }
199
200 fn inner_mut(&mut self) -> &mut OutputNoteRecord {
203 self.update_type = match self.update_type {
204 NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
205 NoteUpdateType::Insert | NoteUpdateType::InsertCommitted => NoteUpdateType::Insert,
208 };
209
210 &mut self.note
211 }
212
213 pub fn update_type(&self) -> &NoteUpdateType {
215 &self.update_type
216 }
217
218 pub fn id(&self) -> NoteId {
220 self.note.id()
221 }
222}
223
224#[derive(Clone, Debug, Default, PartialEq)]
233pub struct NoteUpdateTracker {
234 input_notes: BTreeMap<NoteId, InputNoteUpdate>,
240 expected_input_notes: BTreeMap<NoteDetailsCommitment, InputNoteUpdate>,
243 output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
245 input_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
247 output_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
249 nullifier_order: BTreeMap<Nullifier, u32>,
253}
254
255impl NoteUpdateTracker {
256 pub fn new(
258 input_notes: impl IntoIterator<Item = InputNoteRecord>,
259 output_notes: impl IntoIterator<Item = OutputNoteRecord>,
260 ) -> Self {
261 let mut tracker = Self::default();
262 for note in input_notes {
263 tracker.insert_input_note(note, NoteUpdateType::None);
264 }
265 for note in output_notes {
266 tracker.insert_output_note(note, NoteUpdateType::None);
267 }
268
269 tracker
270 }
271
272 pub fn for_transaction_updates(
280 new_input_notes: impl IntoIterator<Item = InputNoteRecord>,
281 updated_input_notes: impl IntoIterator<Item = InputNoteRecord>,
282 new_output_notes: impl IntoIterator<Item = OutputNoteRecord>,
283 ) -> Self {
284 let mut tracker = Self::default();
285
286 for note in new_input_notes {
287 tracker.insert_input_note(note, NoteUpdateType::Insert);
288 }
289
290 for note in updated_input_notes {
291 tracker.insert_input_note(note, NoteUpdateType::Update);
292 }
293
294 for note in new_output_notes {
295 tracker.insert_output_note(note, NoteUpdateType::Insert);
296 }
297
298 tracker
299 }
300
301 pub fn updated_input_notes(&self) -> impl Iterator<Item = &InputNoteUpdate> {
315 self.input_notes
316 .values()
317 .chain(self.expected_input_notes.values())
318 .filter(|note| note.update_type.is_modified())
319 }
320
321 pub fn consumed_input_note_ids(&self) -> impl Iterator<Item = NoteId> + '_ {
325 self.input_notes
326 .iter()
327 .filter(|(_, update)| update.update_type.is_modified() && update.inner().is_consumed())
328 .map(|(note_id, _)| *note_id)
329 }
330
331 pub fn consumed_note_ids(&self) -> impl Iterator<Item = NoteId> + '_ {
334 let output = self.output_notes.iter().filter_map(|(note_id, update)| {
335 (update.update_type.is_modified() && update.inner().is_consumed()).then_some(*note_id)
336 });
337 self.consumed_input_note_ids().chain(output)
338 }
339
340 pub fn updated_output_notes(&self) -> impl Iterator<Item = &OutputNoteUpdate> {
346 self.output_notes.values().filter(|note| note.update_type.is_modified())
347 }
348
349 pub fn is_empty(&self) -> bool {
351 self.input_notes.is_empty()
352 && self.output_notes.is_empty()
353 && self.expected_input_notes.is_empty()
354 }
355
356 pub fn unspent_nullifiers(&self) -> impl Iterator<Item = Nullifier> {
358 let input_note_unspent_nullifiers = self
359 .input_notes
360 .values()
361 .filter(|note| !note.inner().is_consumed())
362 .filter_map(|note| note.inner().nullifier());
363
364 let output_note_unspent_nullifiers = self
365 .output_notes
366 .values()
367 .filter(|note| !note.inner().is_consumed())
368 .filter_map(|note| note.inner().nullifier());
369
370 input_note_unspent_nullifiers.chain(output_note_unspent_nullifiers)
371 }
372
373 pub fn extend_nullifiers(&mut self, nullifiers: impl IntoIterator<Item = Nullifier>) {
378 for nullifier in nullifiers {
379 let next_pos =
380 u32::try_from(self.nullifier_order.len()).expect("nullifier count exceeds u32");
381 self.nullifier_order.entry(nullifier).or_insert(next_pos);
382 }
383 }
384
385 pub(crate) fn apply_new_public_note(
392 &mut self,
393 mut public_note_data: InputNoteRecord,
394 block_header: &BlockHeader,
395 ) -> Result<(), ClientError> {
396 public_note_data.block_header_received(block_header)?;
397 self.insert_input_note(public_note_data, NoteUpdateType::Insert);
398
399 Ok(())
400 }
401
402 pub(crate) fn apply_committed_note_state_transitions(
405 &mut self,
406 committed_note: &CommittedNote,
407 block_header: &BlockHeader,
408 attachments: Option<&NoteAttachments>,
409 ) -> Result<bool, ClientError> {
410 let inclusion_proof = committed_note.inclusion_proof().clone();
411 let metadata = *committed_note.metadata();
412 let note_id = *committed_note.note_id();
413
414 let is_tracked_as_input_note =
415 if let Some(input_note_record) = self.get_input_note_by_id(note_id) {
416 input_note_record.inclusion_proof_received(inclusion_proof.clone(), metadata)?;
417 input_note_record.block_header_received(block_header)?;
418 if let Some(attachments) = attachments {
419 input_note_record.set_attachments(attachments.clone());
420 }
421
422 true
423 } else if let Some(commitment) = self.expected_note_matching(note_id, &metadata) {
424 let mut update = self
427 .expected_input_notes
428 .remove(&commitment)
429 .expect("commitment was just matched against the expected notes");
430 let record = &mut update.note;
431 record.inclusion_proof_received(inclusion_proof.clone(), metadata)?;
432 record.block_header_received(block_header)?;
433 if let Some(attachments) = attachments {
434 record.set_attachments(attachments.clone());
435 }
436
437 let nullifier = record.nullifier().expect("note with an id has metadata");
441 self.input_notes_by_nullifier.insert(nullifier, note_id);
442 self.input_notes
443 .insert(note_id, InputNoteUpdate::new_insert_committed(update.note));
444
445 true
446 } else {
447 false
448 };
449
450 self.try_commit_output_note(note_id, inclusion_proof)?;
451
452 Ok(is_tracked_as_input_note)
453 }
454
455 pub(crate) fn apply_output_note_inclusion_proofs(
460 &mut self,
461 committed_notes: &[CommittedNote],
462 ) -> Result<(), ClientError> {
463 for committed_note in committed_notes {
464 self.try_commit_output_note(
465 *committed_note.note_id(),
466 committed_note.inclusion_proof().clone(),
467 )?;
468 }
469 Ok(())
470 }
471
472 pub(crate) fn mark_erased_note_as_consumed(
482 &mut self,
483 note_header: &NoteHeader,
484 block_num: BlockNumber,
485 ) -> Result<(), ClientError> {
486 let note_id = note_header.id();
487
488 if let Some(output_note) = self.get_output_note_by_id(note_id)
489 && !output_note.is_consumed()
490 && !output_note.is_committed()
491 && let Some(nullifier) = output_note.nullifier()
492 {
493 output_note.nullifier_received(nullifier, block_num)?;
494 }
495
496 if let Some(input_note_update) = self.input_notes.get_mut(¬e_id)
497 && !input_note_update.inner().is_consumed()
498 && let Some(nullifier) = input_note_update.inner().nullifier()
499 {
500 let consumer_account =
501 NetworkAccountTarget::try_from(input_note_update.inner().attachments())
502 .ok()
503 .map(|target| target.target_id());
504 input_note_update.inner_mut().consumed_externally(
505 nullifier,
506 block_num,
507 consumer_account,
508 )?;
509 input_note_update.inner_mut().set_consumed_tx_order(Some(0));
510 }
511
512 Ok(())
513 }
514
515 fn try_insert_consumed_input_from_output(
522 &mut self,
523 note_id: NoteId,
524 consumer: AccountId,
525 block_num: BlockNumber,
526 consumed_tx_order: Option<u32>,
527 ) -> Result<(), ClientError> {
528 if self.input_notes.contains_key(¬e_id) {
529 return Ok(());
530 }
531 let Some(output_note) = self.output_notes.get(¬e_id) else {
532 return Ok(());
533 };
534 let Ok(note) = Note::try_from(output_note.inner().clone()) else {
535 return Ok(());
536 };
537
538 let mut input_record = InputNoteRecord::from(note);
539 let nullifier =
540 input_record.nullifier().expect("record built from a full note has metadata");
541 input_record.consumed_externally(nullifier, block_num, Some(consumer))?;
542 input_record.set_consumed_tx_order(consumed_tx_order);
543 self.insert_input_note(input_record, NoteUpdateType::Insert);
544 Ok(())
545 }
546
547 fn try_commit_output_note(
550 &mut self,
551 note_id: NoteId,
552 inclusion_proof: NoteInclusionProof,
553 ) -> Result<(), ClientError> {
554 if let Some(output_note) = self.get_output_note_by_id(note_id) {
555 output_note.inclusion_proof_received(inclusion_proof)?;
556 }
557 Ok(())
558 }
559
560 pub(crate) fn apply_note_consumption<'a>(
574 &mut self,
575 consumption: &NoteConsumption,
576 mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
577 ) -> Result<(), ClientError> {
578 let nullifier = consumption.nullifier;
579 let block_num = consumption.block_num;
580 let external_consumer = consumption.external_consumer;
581 let order = self.get_nullifier_order(nullifier);
582 let input_present = self.input_notes_by_nullifier.contains_key(&nullifier);
583
584 if let Some(input_note_update) = self.get_input_note_update_by_nullifier(nullifier) {
585 if let Some(consumer_transaction) = committed_transactions
586 .find(|t| input_note_update.inner().consumer_transaction_id() == Some(&t.id))
587 {
588 if let TransactionStatus::Committed { block_number, .. } =
590 consumer_transaction.status
591 {
592 input_note_update
593 .inner_mut()
594 .transaction_committed(consumer_transaction.id, block_number)?;
595 }
596 } else {
597 input_note_update.inner_mut().consumed_externally(
600 nullifier,
601 block_num,
602 external_consumer,
603 )?;
604 }
605 input_note_update.inner_mut().set_consumed_tx_order(order);
606 }
607
608 if let Some(output_note_record) = self.get_output_note_by_nullifier(nullifier) {
609 output_note_record.nullifier_received(nullifier, block_num)?;
610 }
611
612 if !input_present
613 && let Some(consumer) = external_consumer
614 && let Some(note_id) = self.output_notes_by_nullifier.get(&nullifier).copied()
615 {
616 self.try_insert_consumed_input_from_output(note_id, consumer, block_num, order)?;
617 }
618
619 Ok(())
620 }
621
622 fn get_nullifier_order(&self, nullifier: Nullifier) -> Option<u32> {
628 self.nullifier_order.get(&nullifier).copied()
629 }
630
631 fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
633 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
634 }
635
636 fn expected_note_matching(
639 &self,
640 note_id: NoteId,
641 metadata: &NoteMetadata,
642 ) -> Option<NoteDetailsCommitment> {
643 self.expected_input_notes
644 .keys()
645 .copied()
646 .find(|commitment| NoteId::new(*commitment, metadata) == note_id)
647 }
648
649 fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
651 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
652 }
653
654 fn get_input_note_update_by_nullifier(
657 &mut self,
658 nullifier: Nullifier,
659 ) -> Option<&mut InputNoteUpdate> {
660 let note_id = self.input_notes_by_nullifier.get(&nullifier).copied()?;
661 self.input_notes.get_mut(¬e_id)
662 }
663
664 fn get_output_note_by_nullifier(
667 &mut self,
668 nullifier: Nullifier,
669 ) -> Option<&mut OutputNoteRecord> {
670 let note_id = self.output_notes_by_nullifier.get(&nullifier).copied()?;
671 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
672 }
673
674 fn insert_input_note(&mut self, note: InputNoteRecord, update_type: NoteUpdateType) {
676 let update = match update_type {
677 NoteUpdateType::None => InputNoteUpdate::new_none(note),
678 NoteUpdateType::Insert => InputNoteUpdate::new_insert(note),
679 NoteUpdateType::Update => InputNoteUpdate::new_update(note),
680 NoteUpdateType::InsertCommitted => InputNoteUpdate::new_insert_committed(note),
681 };
682
683 if let Some(note_id) = update.inner().id() {
684 self.expected_input_notes.remove(&update.inner().details_commitment());
686 let nullifier = update.inner().nullifier().expect("note with an id has metadata");
687 self.input_notes_by_nullifier.insert(nullifier, note_id);
688 self.input_notes.insert(note_id, update);
689 } else {
690 let commitment = update.inner().details_commitment();
693 self.expected_input_notes.insert(commitment, update);
694 }
695 }
696
697 fn insert_output_note(&mut self, note: OutputNoteRecord, update_type: NoteUpdateType) {
699 let note_id = note.id();
700 if let Some(nullifier) = note.nullifier() {
701 self.output_notes_by_nullifier.insert(nullifier, note_id);
702 }
703 let update = match update_type {
704 NoteUpdateType::None => OutputNoteUpdate::new_none(note),
705 NoteUpdateType::Update => OutputNoteUpdate::new_update(note),
706 NoteUpdateType::Insert | NoteUpdateType::InsertCommitted => {
709 OutputNoteUpdate::new_insert(note)
710 },
711 };
712 self.output_notes.insert(note_id, update);
713 }
714}
715
716impl Serializable for NoteUpdateType {
720 fn write_into<W: ByteWriter>(&self, target: &mut W) {
721 target.write_u8(*self as u8);
722 }
723}
724
725impl Deserializable for NoteUpdateType {
726 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
727 NoteUpdateType::try_from(source.read_u8()?).map_err(|val| {
728 DeserializationError::InvalidValue(format!("invalid note update type: {val}"))
729 })
730 }
731}
732
733impl Serializable for InputNoteUpdate {
734 fn write_into<W: ByteWriter>(&self, target: &mut W) {
735 self.note.write_into(target);
736 self.update_type.write_into(target);
737 }
738}
739
740impl Deserializable for InputNoteUpdate {
741 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
742 let note = InputNoteRecord::read_from(source)?;
743 let update_type = NoteUpdateType::read_from(source)?;
744 Ok(Self { note, update_type })
745 }
746}
747
748impl Serializable for OutputNoteUpdate {
749 fn write_into<W: ByteWriter>(&self, target: &mut W) {
750 self.note.write_into(target);
751 self.update_type.write_into(target);
752 }
753}
754
755impl Deserializable for OutputNoteUpdate {
756 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
757 let note = OutputNoteRecord::read_from(source)?;
758 let update_type = NoteUpdateType::read_from(source)?;
759 Ok(Self { note, update_type })
760 }
761}
762
763impl Serializable for NoteUpdateTracker {
764 fn write_into<W: ByteWriter>(&self, target: &mut W) {
765 self.input_notes.write_into(target);
768 self.output_notes.write_into(target);
769 self.expected_input_notes.write_into(target);
770 self.nullifier_order.write_into(target);
771 }
772}
773
774impl Deserializable for NoteUpdateTracker {
775 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
776 let input_notes = BTreeMap::<NoteId, InputNoteUpdate>::read_from(source)?;
777 let output_notes = BTreeMap::<NoteId, OutputNoteUpdate>::read_from(source)?;
778 let expected_input_notes =
779 BTreeMap::<NoteDetailsCommitment, InputNoteUpdate>::read_from(source)?;
780 let nullifier_order = BTreeMap::<Nullifier, u32>::read_from(source)?;
781
782 let input_notes_by_nullifier = input_notes
783 .iter()
784 .map(|(note_id, update)| {
785 (update.inner().nullifier().expect("note with an id has metadata"), *note_id)
786 })
787 .collect();
788 let output_notes_by_nullifier = output_notes
789 .iter()
790 .filter_map(|(note_id, update)| {
791 update.inner().nullifier().map(|nullifier| (nullifier, *note_id))
792 })
793 .collect();
794
795 Ok(Self {
796 input_notes,
797 expected_input_notes,
798 output_notes,
799 input_notes_by_nullifier,
800 output_notes_by_nullifier,
801 nullifier_order,
802 })
803 }
804}