1use alloc::collections::BTreeMap;
2
3use miden_objects::block::BlockHeader;
4use miden_objects::note::{NoteId, NoteInclusionProof, Nullifier};
5
6use crate::ClientError;
7use crate::rpc::domain::note::CommittedNote;
8use crate::rpc::domain::nullifier::NullifierUpdate;
9use crate::store::{InputNoteRecord, OutputNoteRecord};
10use crate::transaction::{TransactionRecord, TransactionStatus};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum NoteUpdateType {
19 None,
21 Insert,
23 Update,
25}
26
27#[derive(Clone, Debug)]
29pub struct InputNoteUpdate {
30 note: InputNoteRecord,
32 update_type: NoteUpdateType,
34}
35
36impl InputNoteUpdate {
37 fn new_none(note: InputNoteRecord) -> Self {
39 Self { note, update_type: NoteUpdateType::None }
40 }
41
42 fn new_insert(note: InputNoteRecord) -> Self {
44 Self {
45 note,
46 update_type: NoteUpdateType::Insert,
47 }
48 }
49
50 fn new_update(note: InputNoteRecord) -> Self {
52 Self {
53 note,
54 update_type: NoteUpdateType::Update,
55 }
56 }
57
58 pub fn inner(&self) -> &InputNoteRecord {
60 &self.note
61 }
62
63 fn inner_mut(&mut self) -> &mut InputNoteRecord {
65 self.update_type = match self.update_type {
66 NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
67 NoteUpdateType::Insert => NoteUpdateType::Insert,
68 };
69
70 &mut self.note
71 }
72
73 pub fn update_type(&self) -> &NoteUpdateType {
75 &self.update_type
76 }
77
78 pub fn id(&self) -> NoteId {
80 self.note.id()
81 }
82}
83
84#[derive(Clone, Debug)]
86pub struct OutputNoteUpdate {
87 note: OutputNoteRecord,
89 update_type: NoteUpdateType,
91}
92
93impl OutputNoteUpdate {
94 fn new_none(note: OutputNoteRecord) -> Self {
96 Self { note, update_type: NoteUpdateType::None }
97 }
98
99 fn new_insert(note: OutputNoteRecord) -> Self {
101 Self {
102 note,
103 update_type: NoteUpdateType::Insert,
104 }
105 }
106
107 fn new_update(note: OutputNoteRecord) -> Self {
109 Self {
110 note,
111 update_type: NoteUpdateType::Update,
112 }
113 }
114
115 pub fn inner(&self) -> &OutputNoteRecord {
117 &self.note
118 }
119
120 fn inner_mut(&mut self) -> &mut OutputNoteRecord {
123 self.update_type = match self.update_type {
124 NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
125 NoteUpdateType::Insert => NoteUpdateType::Insert,
126 };
127
128 &mut self.note
129 }
130
131 pub fn update_type(&self) -> &NoteUpdateType {
133 &self.update_type
134 }
135
136 pub fn id(&self) -> NoteId {
138 self.note.id()
139 }
140}
141
142#[derive(Clone, Debug, Default)]
151pub struct NoteUpdateTracker {
152 input_notes: BTreeMap<NoteId, InputNoteUpdate>,
154 output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
156 input_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
158 output_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
160}
161
162impl NoteUpdateTracker {
163 pub fn new(
165 input_notes: impl IntoIterator<Item = InputNoteRecord>,
166 output_notes: impl IntoIterator<Item = OutputNoteRecord>,
167 ) -> Self {
168 let mut tracker = Self::default();
169 for note in input_notes {
170 tracker.insert_input_note(note, NoteUpdateType::None);
171 }
172 for note in output_notes {
173 tracker.insert_output_note(note, NoteUpdateType::None);
174 }
175
176 tracker
177 }
178
179 pub fn for_transaction_updates(
187 new_input_notes: impl IntoIterator<Item = InputNoteRecord>,
188 updated_input_notes: impl IntoIterator<Item = InputNoteRecord>,
189 new_output_notes: impl IntoIterator<Item = OutputNoteRecord>,
190 ) -> Self {
191 let mut tracker = Self::default();
192
193 for note in new_input_notes {
194 tracker.insert_input_note(note, NoteUpdateType::Insert);
195 }
196
197 for note in updated_input_notes {
198 tracker.insert_input_note(note, NoteUpdateType::Update);
199 }
200
201 for note in new_output_notes {
202 tracker.insert_output_note(note, NoteUpdateType::Insert);
203 }
204
205 tracker
206 }
207
208 pub fn updated_input_notes(&self) -> impl Iterator<Item = &InputNoteUpdate> {
217 self.input_notes.values().filter(|note| {
218 matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
219 })
220 }
221
222 pub fn updated_output_notes(&self) -> impl Iterator<Item = &OutputNoteUpdate> {
228 self.output_notes.values().filter(|note| {
229 matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
230 })
231 }
232
233 pub fn is_empty(&self) -> bool {
235 self.input_notes.is_empty() && self.output_notes.is_empty()
236 }
237
238 pub fn unspent_nullifiers(&self) -> impl Iterator<Item = Nullifier> {
240 let input_note_unspent_nullifiers = self
241 .input_notes
242 .values()
243 .filter(|note| !note.inner().is_consumed())
244 .map(|note| note.inner().nullifier());
245
246 let output_note_unspent_nullifiers = self
247 .output_notes
248 .values()
249 .filter(|note| !note.inner().is_consumed())
250 .filter_map(|note| note.inner().nullifier());
251
252 input_note_unspent_nullifiers.chain(output_note_unspent_nullifiers)
253 }
254
255 pub(crate) fn apply_new_public_note(
262 &mut self,
263 mut public_note_data: InputNoteRecord,
264 block_header: &BlockHeader,
265 ) -> Result<(), ClientError> {
266 public_note_data.block_header_received(block_header)?;
267 self.insert_input_note(public_note_data, NoteUpdateType::Insert);
268
269 Ok(())
270 }
271
272 pub(crate) fn apply_committed_note_state_transitions(
275 &mut self,
276 committed_note: &CommittedNote,
277 block_header: &BlockHeader,
278 ) -> Result<bool, ClientError> {
279 let inclusion_proof = NoteInclusionProof::new(
280 block_header.block_num(),
281 committed_note.note_index(),
282 committed_note.inclusion_path().clone(),
283 )?;
284 let is_tracked_as_input_note =
285 if let Some(input_note_record) = self.get_input_note_by_id(*committed_note.note_id()) {
286 input_note_record
288 .inclusion_proof_received(inclusion_proof.clone(), committed_note.metadata())?;
289 input_note_record.block_header_received(block_header)?;
290
291 true
292 } else {
293 false
294 };
295
296 if let Some(output_note_record) = self.get_output_note_by_id(*committed_note.note_id()) {
297 output_note_record.inclusion_proof_received(inclusion_proof.clone())?;
299 }
300
301 Ok(is_tracked_as_input_note)
302 }
303
304 pub(crate) fn apply_nullifiers_state_transitions<'a>(
312 &mut self,
313 nullifier_update: &NullifierUpdate,
314 mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
315 ) -> Result<(), ClientError> {
316 if let Some(input_note_record) =
317 self.get_input_note_by_nullifier(nullifier_update.nullifier)
318 {
319 if let Some(consumer_transaction) = committed_transactions
320 .find(|t| input_note_record.consumer_transaction_id() == Some(&t.id))
321 {
322 if let TransactionStatus::Committed { block_number, .. } =
324 consumer_transaction.status
325 {
326 input_note_record
327 .transaction_committed(consumer_transaction.id, block_number)?;
328 }
329 } else {
330 input_note_record
332 .consumed_externally(nullifier_update.nullifier, nullifier_update.block_num)?;
333 }
334 }
335
336 if let Some(output_note_record) =
337 self.get_output_note_by_nullifier(nullifier_update.nullifier)
338 {
339 output_note_record
340 .nullifier_received(nullifier_update.nullifier, nullifier_update.block_num)?;
341 }
342
343 Ok(())
344 }
345
346 fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
351 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
352 }
353
354 fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
356 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
357 }
358
359 fn get_input_note_by_nullifier(
362 &mut self,
363 nullifier: Nullifier,
364 ) -> Option<&mut InputNoteRecord> {
365 let note_id = self.input_notes_by_nullifier.get(&nullifier).copied()?;
366 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
367 }
368
369 fn get_output_note_by_nullifier(
372 &mut self,
373 nullifier: Nullifier,
374 ) -> Option<&mut OutputNoteRecord> {
375 let note_id = self.output_notes_by_nullifier.get(&nullifier).copied()?;
376 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
377 }
378
379 fn insert_input_note(&mut self, note: InputNoteRecord, update_type: NoteUpdateType) {
381 let note_id = note.id();
382 let nullifier = note.nullifier();
383 self.input_notes_by_nullifier.insert(nullifier, note_id);
384 let update = match update_type {
385 NoteUpdateType::None => InputNoteUpdate::new_none(note),
386 NoteUpdateType::Insert => InputNoteUpdate::new_insert(note),
387 NoteUpdateType::Update => InputNoteUpdate::new_update(note),
388 };
389 self.input_notes.insert(note_id, update);
390 }
391
392 fn insert_output_note(&mut self, note: OutputNoteRecord, update_type: NoteUpdateType) {
394 let note_id = note.id();
395 if let Some(nullifier) = note.nullifier() {
396 self.output_notes_by_nullifier.insert(nullifier, note_id);
397 }
398 let update = match update_type {
399 NoteUpdateType::None => OutputNoteUpdate::new_none(note),
400 NoteUpdateType::Insert => OutputNoteUpdate::new_insert(note),
401 NoteUpdateType::Update => OutputNoteUpdate::new_update(note),
402 };
403 self.output_notes.insert(note_id, update);
404 }
405}