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
79#[derive(Clone, Debug)]
81pub struct OutputNoteUpdate {
82 note: OutputNoteRecord,
84 update_type: NoteUpdateType,
86}
87
88impl OutputNoteUpdate {
89 fn new_none(note: OutputNoteRecord) -> Self {
91 Self { note, update_type: NoteUpdateType::None }
92 }
93
94 fn new_insert(note: OutputNoteRecord) -> Self {
96 Self {
97 note,
98 update_type: NoteUpdateType::Insert,
99 }
100 }
101
102 fn new_update(note: OutputNoteRecord) -> Self {
104 Self {
105 note,
106 update_type: NoteUpdateType::Update,
107 }
108 }
109
110 pub fn inner(&self) -> &OutputNoteRecord {
112 &self.note
113 }
114
115 fn inner_mut(&mut self) -> &mut OutputNoteRecord {
118 self.update_type = match self.update_type {
119 NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
120 NoteUpdateType::Insert => NoteUpdateType::Insert,
121 };
122
123 &mut self.note
124 }
125
126 pub fn update_type(&self) -> &NoteUpdateType {
128 &self.update_type
129 }
130}
131
132#[derive(Clone, Debug, Default)]
141pub struct NoteUpdateTracker {
142 input_notes: BTreeMap<NoteId, InputNoteUpdate>,
144 output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
146 input_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
148 output_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
150}
151
152impl NoteUpdateTracker {
153 pub fn new(
155 input_notes: impl IntoIterator<Item = InputNoteRecord>,
156 output_notes: impl IntoIterator<Item = OutputNoteRecord>,
157 ) -> Self {
158 let mut tracker = Self::default();
159 for note in input_notes {
160 tracker.insert_input_note(note, NoteUpdateType::None);
161 }
162 for note in output_notes {
163 tracker.insert_output_note(note, NoteUpdateType::None);
164 }
165
166 tracker
167 }
168
169 pub fn for_transaction_updates(
177 new_input_notes: impl IntoIterator<Item = InputNoteRecord>,
178 updated_input_notes: impl IntoIterator<Item = InputNoteRecord>,
179 new_output_notes: impl IntoIterator<Item = OutputNoteRecord>,
180 ) -> Self {
181 let mut tracker = Self::default();
182
183 for note in new_input_notes {
184 tracker.insert_input_note(note, NoteUpdateType::Insert);
185 }
186
187 for note in updated_input_notes {
188 tracker.insert_input_note(note, NoteUpdateType::Update);
189 }
190
191 for note in new_output_notes {
192 tracker.insert_output_note(note, NoteUpdateType::Insert);
193 }
194
195 tracker
196 }
197
198 pub fn updated_input_notes(&self) -> impl Iterator<Item = &InputNoteUpdate> {
207 self.input_notes.values().filter(|note| {
208 matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
209 })
210 }
211
212 pub fn updated_output_notes(&self) -> impl Iterator<Item = &OutputNoteUpdate> {
218 self.output_notes.values().filter(|note| {
219 matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
220 })
221 }
222
223 pub fn is_empty(&self) -> bool {
225 self.input_notes.is_empty() && self.output_notes.is_empty()
226 }
227
228 pub fn unspent_nullifiers(&self) -> impl Iterator<Item = Nullifier> + '_ {
229 self.input_notes
230 .values()
231 .filter(|note| !note.inner().is_consumed())
232 .map(|note| note.inner().nullifier())
233 }
234
235 pub(crate) fn apply_new_public_note(
242 &mut self,
243 mut public_note_data: InputNoteRecord,
244 block_header: &BlockHeader,
245 ) -> Result<(), ClientError> {
246 public_note_data.block_header_received(block_header)?;
247 self.insert_input_note(public_note_data, NoteUpdateType::Insert);
248
249 Ok(())
250 }
251
252 pub(crate) fn apply_committed_note_state_transitions(
255 &mut self,
256 committed_note: &CommittedNote,
257 block_header: &BlockHeader,
258 ) -> Result<bool, ClientError> {
259 let inclusion_proof = NoteInclusionProof::new(
260 block_header.block_num(),
261 committed_note.note_index(),
262 committed_note.inclusion_path().clone(),
263 )?;
264 let is_tracked_as_input_note =
265 if let Some(input_note_record) = self.get_input_note_by_id(*committed_note.note_id()) {
266 input_note_record
268 .inclusion_proof_received(inclusion_proof.clone(), committed_note.metadata())?;
269 input_note_record.block_header_received(block_header)?;
270
271 true
272 } else {
273 false
274 };
275
276 if let Some(output_note_record) = self.get_output_note_by_id(*committed_note.note_id()) {
277 output_note_record.inclusion_proof_received(inclusion_proof.clone())?;
279 }
280
281 Ok(is_tracked_as_input_note)
282 }
283
284 pub(crate) fn apply_nullifiers_state_transitions<'a>(
292 &mut self,
293 nullifier_update: &NullifierUpdate,
294 mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
295 ) -> Result<(), ClientError> {
296 if let Some(input_note_record) =
297 self.get_input_note_by_nullifier(nullifier_update.nullifier)
298 {
299 if let Some(consumer_transaction) = committed_transactions
300 .find(|t| input_note_record.consumer_transaction_id() == Some(&t.id))
301 {
302 if let TransactionStatus::Committed { block_number, .. } =
304 consumer_transaction.status
305 {
306 input_note_record
307 .transaction_committed(consumer_transaction.id, block_number.as_u32())?;
308 }
309 } else {
310 input_note_record
312 .consumed_externally(nullifier_update.nullifier, nullifier_update.block_num)?;
313 }
314 }
315
316 if let Some(output_note_record) =
317 self.get_output_note_by_nullifier(nullifier_update.nullifier)
318 {
319 output_note_record
320 .nullifier_received(nullifier_update.nullifier, nullifier_update.block_num)?;
321 }
322
323 Ok(())
324 }
325
326 fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
331 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
332 }
333
334 fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
336 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
337 }
338
339 fn get_input_note_by_nullifier(
342 &mut self,
343 nullifier: Nullifier,
344 ) -> Option<&mut InputNoteRecord> {
345 let note_id = self.input_notes_by_nullifier.get(&nullifier).copied()?;
346 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
347 }
348
349 fn get_output_note_by_nullifier(
352 &mut self,
353 nullifier: Nullifier,
354 ) -> Option<&mut OutputNoteRecord> {
355 let note_id = self.output_notes_by_nullifier.get(&nullifier).copied()?;
356 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
357 }
358
359 fn insert_input_note(&mut self, note: InputNoteRecord, update_type: NoteUpdateType) {
361 let note_id = note.id();
362 let nullifier = note.nullifier();
363 self.input_notes_by_nullifier.insert(nullifier, note_id);
364 let update = match update_type {
365 NoteUpdateType::None => InputNoteUpdate::new_none(note),
366 NoteUpdateType::Insert => InputNoteUpdate::new_insert(note),
367 NoteUpdateType::Update => InputNoteUpdate::new_update(note),
368 };
369 self.input_notes.insert(note_id, update);
370 }
371
372 fn insert_output_note(&mut self, note: OutputNoteRecord, update_type: NoteUpdateType) {
374 let note_id = note.id();
375 if let Some(nullifier) = note.nullifier() {
376 self.output_notes_by_nullifier.insert(nullifier, note_id);
377 }
378 let update = match update_type {
379 NoteUpdateType::None => OutputNoteUpdate::new_none(note),
380 NoteUpdateType::Insert => OutputNoteUpdate::new_insert(note),
381 NoteUpdateType::Update => OutputNoteUpdate::new_update(note),
382 };
383 self.output_notes.insert(note_id, update);
384 }
385}