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<(), 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
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
272 if let Some(output_note_record) = self.get_output_note_by_id(*committed_note.note_id()) {
273 output_note_record.inclusion_proof_received(inclusion_proof.clone())?;
275 }
276
277 Ok(())
278 }
279
280 pub(crate) fn apply_nullifiers_state_transitions<'a>(
288 &mut self,
289 nullifier_update: &NullifierUpdate,
290 mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
291 ) -> Result<(), ClientError> {
292 if let Some(input_note_record) =
293 self.get_input_note_by_nullifier(nullifier_update.nullifier)
294 {
295 if let Some(consumer_transaction) = committed_transactions
296 .find(|t| input_note_record.consumer_transaction_id() == Some(&t.id))
297 {
298 if let TransactionStatus::Committed { block_number, .. } =
300 consumer_transaction.status
301 {
302 input_note_record
303 .transaction_committed(consumer_transaction.id, block_number.as_u32())?;
304 }
305 } else {
306 input_note_record
308 .consumed_externally(nullifier_update.nullifier, nullifier_update.block_num)?;
309 }
310 }
311
312 if let Some(output_note_record) =
313 self.get_output_note_by_nullifier(nullifier_update.nullifier)
314 {
315 output_note_record
316 .nullifier_received(nullifier_update.nullifier, nullifier_update.block_num)?;
317 }
318
319 Ok(())
320 }
321
322 fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
327 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
328 }
329
330 fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
332 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
333 }
334
335 fn get_input_note_by_nullifier(
338 &mut self,
339 nullifier: Nullifier,
340 ) -> Option<&mut InputNoteRecord> {
341 let note_id = self.input_notes_by_nullifier.get(&nullifier).copied()?;
342 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
343 }
344
345 fn get_output_note_by_nullifier(
348 &mut self,
349 nullifier: Nullifier,
350 ) -> Option<&mut OutputNoteRecord> {
351 let note_id = self.output_notes_by_nullifier.get(&nullifier).copied()?;
352 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
353 }
354
355 fn insert_input_note(&mut self, note: InputNoteRecord, update_type: NoteUpdateType) {
357 let note_id = note.id();
358 let nullifier = note.nullifier();
359 self.input_notes_by_nullifier.insert(nullifier, note_id);
360 let update = match update_type {
361 NoteUpdateType::None => InputNoteUpdate::new_none(note),
362 NoteUpdateType::Insert => InputNoteUpdate::new_insert(note),
363 NoteUpdateType::Update => InputNoteUpdate::new_update(note),
364 };
365 self.input_notes.insert(note_id, update);
366 }
367
368 fn insert_output_note(&mut self, note: OutputNoteRecord, update_type: NoteUpdateType) {
370 let note_id = note.id();
371 if let Some(nullifier) = note.nullifier() {
372 self.output_notes_by_nullifier.insert(nullifier, note_id);
373 }
374 let update = match update_type {
375 NoteUpdateType::None => OutputNoteUpdate::new_none(note),
376 NoteUpdateType::Insert => OutputNoteUpdate::new_insert(note),
377 NoteUpdateType::Update => OutputNoteUpdate::new_update(note),
378 };
379 self.output_notes.insert(note_id, update);
380 }
381}