1use alloc::collections::{BTreeMap, BTreeSet};
2
3use miden_protocol::account::AccountId;
4use miden_protocol::block::{BlockHeader, BlockNumber};
5use miden_protocol::note::{Note, NoteHeader, NoteId, NoteInclusionProof, Nullifier};
6use miden_standards::note::NetworkAccountTarget;
7
8use crate::ClientError;
9use crate::rpc::RpcError;
10use crate::rpc::domain::note::CommittedNote;
11use crate::rpc::domain::nullifier::NullifierUpdate;
12use crate::store::{InputNoteRecord, OutputNoteRecord};
13use crate::transaction::{TransactionRecord, TransactionStatus};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum NoteUpdateType {
22 None,
24 Insert,
26 Update,
28}
29
30#[derive(Clone, Debug)]
32pub struct InputNoteUpdate {
33 note: InputNoteRecord,
35 update_type: NoteUpdateType,
37}
38
39impl InputNoteUpdate {
40 fn new_none(note: InputNoteRecord) -> Self {
42 Self { note, update_type: NoteUpdateType::None }
43 }
44
45 fn new_insert(note: InputNoteRecord) -> Self {
47 Self {
48 note,
49 update_type: NoteUpdateType::Insert,
50 }
51 }
52
53 fn new_update(note: InputNoteRecord) -> Self {
55 Self {
56 note,
57 update_type: NoteUpdateType::Update,
58 }
59 }
60
61 pub fn inner(&self) -> &InputNoteRecord {
63 &self.note
64 }
65
66 fn inner_mut(&mut self) -> &mut InputNoteRecord {
69 self.update_type = match self.update_type {
70 NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
71 NoteUpdateType::Insert => NoteUpdateType::Insert,
72 };
73
74 &mut self.note
75 }
76
77 pub fn update_type(&self) -> &NoteUpdateType {
79 &self.update_type
80 }
81
82 pub fn id(&self) -> NoteId {
84 self.note.id()
85 }
86
87 pub fn consumed_tx_order(&self) -> Option<u32> {
91 self.note.state().consumed_tx_order()
92 }
93}
94
95#[derive(Clone, Debug)]
97pub struct OutputNoteUpdate {
98 note: OutputNoteRecord,
100 update_type: NoteUpdateType,
102}
103
104impl OutputNoteUpdate {
105 fn new_none(note: OutputNoteRecord) -> Self {
107 Self { note, update_type: NoteUpdateType::None }
108 }
109
110 fn new_insert(note: OutputNoteRecord) -> Self {
112 Self {
113 note,
114 update_type: NoteUpdateType::Insert,
115 }
116 }
117
118 fn new_update(note: OutputNoteRecord) -> Self {
120 Self {
121 note,
122 update_type: NoteUpdateType::Update,
123 }
124 }
125
126 pub fn inner(&self) -> &OutputNoteRecord {
128 &self.note
129 }
130
131 fn inner_mut(&mut self) -> &mut OutputNoteRecord {
134 self.update_type = match self.update_type {
135 NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
136 NoteUpdateType::Insert => NoteUpdateType::Insert,
137 };
138
139 &mut self.note
140 }
141
142 pub fn update_type(&self) -> &NoteUpdateType {
144 &self.update_type
145 }
146
147 pub fn id(&self) -> NoteId {
149 self.note.id()
150 }
151}
152
153#[derive(Clone, Debug, Default)]
162pub struct NoteUpdateTracker {
163 input_notes: BTreeMap<NoteId, InputNoteUpdate>,
165 output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
167 input_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
169 output_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
171 nullifier_order: BTreeMap<Nullifier, u32>,
175 tracked_accounts_ids: BTreeSet<AccountId>,
178}
179
180impl NoteUpdateTracker {
181 pub fn new(
183 input_notes: impl IntoIterator<Item = InputNoteRecord>,
184 output_notes: impl IntoIterator<Item = OutputNoteRecord>,
185 ) -> Self {
186 let mut tracker = Self::default();
187 for note in input_notes {
188 tracker.insert_input_note(note, NoteUpdateType::None);
189 }
190 for note in output_notes {
191 tracker.insert_output_note(note, NoteUpdateType::None);
192 }
193
194 tracker
195 }
196
197 #[must_use]
199 pub fn with_tracked_accounts(mut self, tracked_accounts_ids: BTreeSet<AccountId>) -> Self {
200 self.tracked_accounts_ids = tracked_accounts_ids;
201 self
202 }
203
204 pub fn for_transaction_updates(
212 new_input_notes: impl IntoIterator<Item = InputNoteRecord>,
213 updated_input_notes: impl IntoIterator<Item = InputNoteRecord>,
214 new_output_notes: impl IntoIterator<Item = OutputNoteRecord>,
215 ) -> Self {
216 let mut tracker = Self::default();
217
218 for note in new_input_notes {
219 tracker.insert_input_note(note, NoteUpdateType::Insert);
220 }
221
222 for note in updated_input_notes {
223 tracker.insert_input_note(note, NoteUpdateType::Update);
224 }
225
226 for note in new_output_notes {
227 tracker.insert_output_note(note, NoteUpdateType::Insert);
228 }
229
230 tracker
231 }
232
233 pub fn updated_input_notes(&self) -> impl Iterator<Item = &InputNoteUpdate> {
242 self.input_notes.values().filter(|note| {
243 matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
244 })
245 }
246
247 pub fn updated_output_notes(&self) -> impl Iterator<Item = &OutputNoteUpdate> {
253 self.output_notes.values().filter(|note| {
254 matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
255 })
256 }
257
258 pub fn is_empty(&self) -> bool {
260 self.input_notes.is_empty() && self.output_notes.is_empty()
261 }
262
263 pub fn unspent_nullifiers(&self) -> impl Iterator<Item = Nullifier> {
265 let input_note_unspent_nullifiers = self
266 .input_notes
267 .values()
268 .filter(|note| !note.inner().is_consumed())
269 .map(|note| note.inner().nullifier());
270
271 let output_note_unspent_nullifiers = self
272 .output_notes
273 .values()
274 .filter(|note| !note.inner().is_consumed())
275 .filter_map(|note| note.inner().nullifier());
276
277 input_note_unspent_nullifiers.chain(output_note_unspent_nullifiers)
278 }
279
280 pub fn extend_nullifiers(&mut self, nullifiers: impl IntoIterator<Item = Nullifier>) {
285 for nullifier in nullifiers {
286 let next_pos =
287 u32::try_from(self.nullifier_order.len()).expect("nullifier count exceeds u32");
288 self.nullifier_order.entry(nullifier).or_insert(next_pos);
289 }
290 }
291
292 pub(crate) fn apply_new_public_note(
299 &mut self,
300 mut public_note_data: InputNoteRecord,
301 block_header: &BlockHeader,
302 ) -> Result<(), ClientError> {
303 public_note_data.block_header_received(block_header)?;
304 self.insert_input_note(public_note_data, NoteUpdateType::Insert);
305
306 Ok(())
307 }
308
309 pub(crate) fn apply_committed_note_state_transitions(
312 &mut self,
313 committed_note: &CommittedNote,
314 block_header: &BlockHeader,
315 ) -> Result<bool, ClientError> {
316 let inclusion_proof = committed_note.inclusion_proof().clone();
317
318 let is_tracked_as_input_note =
319 if let Some(input_note_record) = self.get_input_note_by_id(*committed_note.note_id()) {
320 let metadata = committed_note.metadata().cloned().ok_or_else(|| {
321 ClientError::RpcError(RpcError::ExpectedDataMissing(format!(
322 "full metadata for committed note {}",
323 committed_note.note_id()
324 )))
325 })?;
326 input_note_record.inclusion_proof_received(inclusion_proof.clone(), metadata)?;
327 input_note_record.block_header_received(block_header)?;
328
329 true
330 } else {
331 false
332 };
333
334 self.try_commit_output_note(*committed_note.note_id(), inclusion_proof)?;
335
336 Ok(is_tracked_as_input_note)
337 }
338
339 pub(crate) fn apply_output_note_inclusion_proofs(
344 &mut self,
345 committed_notes: &[CommittedNote],
346 ) -> Result<(), ClientError> {
347 for committed_note in committed_notes {
348 self.try_commit_output_note(
349 *committed_note.note_id(),
350 committed_note.inclusion_proof().clone(),
351 )?;
352 }
353 Ok(())
354 }
355
356 pub(crate) fn mark_erased_note_as_consumed(
362 &mut self,
363 note_header: &NoteHeader,
364 block_num: BlockNumber,
365 ) -> Result<(), ClientError> {
366 let note_id = note_header.id();
367
368 if let Some(output_note) = self.get_output_note_by_id(note_id)
369 && !output_note.is_consumed()
370 && !output_note.is_committed()
371 && let Some(nullifier) = output_note.nullifier()
372 {
373 output_note.nullifier_received(nullifier, block_num)?;
374 }
375
376 let consumer_network_account_id =
379 NetworkAccountTarget::try_from(note_header.metadata().attachment())
380 .ok()
381 .map(|t| t.target_id())
382 .filter(|id| self.tracked_accounts_ids.contains(id));
383
384 if let Some(consumer_id) = consumer_network_account_id {
386 self.try_insert_consumed_input_from_output(note_id, consumer_id, block_num, Some(0))?;
387 }
388
389 if let Some(input_note_update) = self.input_notes.get_mut(¬e_id)
391 && !input_note_update.inner().is_consumed()
392 {
393 let nullifier = input_note_update.inner().nullifier();
394 input_note_update.inner_mut().consumed_externally(
395 nullifier,
396 block_num,
397 consumer_network_account_id,
398 )?;
399 input_note_update.inner_mut().set_consumed_tx_order(Some(0));
400 }
401
402 Ok(())
403 }
404
405 fn try_insert_consumed_input_from_output(
412 &mut self,
413 note_id: NoteId,
414 consumer: AccountId,
415 block_num: BlockNumber,
416 consumed_tx_order: Option<u32>,
417 ) -> Result<(), ClientError> {
418 if self.input_notes.contains_key(¬e_id) {
419 return Ok(());
420 }
421 let Some(output_note) = self.output_notes.get(¬e_id) else {
422 return Ok(());
423 };
424 let Ok(note) = Note::try_from(output_note.inner().clone()) else {
425 return Ok(());
426 };
427
428 let mut input_record = InputNoteRecord::from(note);
429 let nullifier = input_record.nullifier();
430 input_record.consumed_externally(nullifier, block_num, Some(consumer))?;
431 input_record.set_consumed_tx_order(consumed_tx_order);
432 self.insert_input_note(input_record, NoteUpdateType::Insert);
433 Ok(())
434 }
435
436 fn try_commit_output_note(
439 &mut self,
440 note_id: NoteId,
441 inclusion_proof: NoteInclusionProof,
442 ) -> Result<(), ClientError> {
443 if let Some(output_note) = self.get_output_note_by_id(note_id) {
444 output_note.inclusion_proof_received(inclusion_proof)?;
445 }
446 Ok(())
447 }
448
449 pub(crate) fn apply_nullifiers_state_transitions<'a>(
463 &mut self,
464 nullifier_update: &NullifierUpdate,
465 mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
466 external_consumer_account: Option<AccountId>,
467 ) -> Result<(), ClientError> {
468 let nullifier = nullifier_update.nullifier;
469 let block_num = nullifier_update.block_num;
470 let order = self.get_nullifier_order(nullifier);
471 let input_present = self.input_notes_by_nullifier.contains_key(&nullifier);
472
473 if let Some(input_note_update) = self.get_input_note_update_by_nullifier(nullifier) {
474 if let Some(consumer_transaction) = committed_transactions
475 .find(|t| input_note_update.inner().consumer_transaction_id() == Some(&t.id))
476 {
477 if let TransactionStatus::Committed { block_number, .. } =
479 consumer_transaction.status
480 {
481 input_note_update
482 .inner_mut()
483 .transaction_committed(consumer_transaction.id, block_number)?;
484 }
485 } else {
486 input_note_update.inner_mut().consumed_externally(
489 nullifier,
490 block_num,
491 external_consumer_account,
492 )?;
493 }
494 input_note_update.inner_mut().set_consumed_tx_order(order);
495 }
496
497 if let Some(output_note_record) = self.get_output_note_by_nullifier(nullifier) {
498 output_note_record.nullifier_received(nullifier, block_num)?;
499 }
500
501 if !input_present
502 && let Some(consumer) = external_consumer_account
503 && let Some(note_id) = self.output_notes_by_nullifier.get(&nullifier).copied()
504 {
505 self.try_insert_consumed_input_from_output(note_id, consumer, block_num, order)?;
506 }
507
508 Ok(())
509 }
510
511 fn get_nullifier_order(&self, nullifier: Nullifier) -> Option<u32> {
517 self.nullifier_order.get(&nullifier).copied()
518 }
519
520 fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
522 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
523 }
524
525 fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
527 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
528 }
529
530 fn get_input_note_update_by_nullifier(
533 &mut self,
534 nullifier: Nullifier,
535 ) -> Option<&mut InputNoteUpdate> {
536 let note_id = self.input_notes_by_nullifier.get(&nullifier).copied()?;
537 self.input_notes.get_mut(¬e_id)
538 }
539
540 fn get_output_note_by_nullifier(
543 &mut self,
544 nullifier: Nullifier,
545 ) -> Option<&mut OutputNoteRecord> {
546 let note_id = self.output_notes_by_nullifier.get(&nullifier).copied()?;
547 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
548 }
549
550 fn insert_input_note(&mut self, note: InputNoteRecord, update_type: NoteUpdateType) {
552 let note_id = note.id();
553 let nullifier = note.nullifier();
554 self.input_notes_by_nullifier.insert(nullifier, note_id);
555 let update = match update_type {
556 NoteUpdateType::None => InputNoteUpdate::new_none(note),
557 NoteUpdateType::Insert => InputNoteUpdate::new_insert(note),
558 NoteUpdateType::Update => InputNoteUpdate::new_update(note),
559 };
560 self.input_notes.insert(note_id, update);
561 }
562
563 fn insert_output_note(&mut self, note: OutputNoteRecord, update_type: NoteUpdateType) {
565 let note_id = note.id();
566 if let Some(nullifier) = note.nullifier() {
567 self.output_notes_by_nullifier.insert(nullifier, note_id);
568 }
569 let update = match update_type {
570 NoteUpdateType::None => OutputNoteUpdate::new_none(note),
571 NoteUpdateType::Insert => OutputNoteUpdate::new_insert(note),
572 NoteUpdateType::Update => OutputNoteUpdate::new_update(note),
573 };
574 self.output_notes.insert(note_id, update);
575 }
576}