miden_client/note/
note_update_tracker.rs

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// NOTE UPDATE
13// ================================================================================================
14
15/// Represents the possible types of updates that can be applied to a note in a
16/// [`NoteUpdateTracker`].
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum NoteUpdateType {
19    /// Indicates that the note was already tracked but it was not updated.
20    None,
21    /// Indicates that the note is new and should be inserted in the store.
22    Insert,
23    /// Indicates that the note was already tracked and should be updated.
24    Update,
25}
26
27/// Represents the possible states of an input note record in a [`NoteUpdateTracker`].
28#[derive(Clone, Debug)]
29pub struct InputNoteUpdate {
30    /// Input note being updated.
31    note: InputNoteRecord,
32    /// Type of the note update.
33    update_type: NoteUpdateType,
34}
35
36impl InputNoteUpdate {
37    /// Creates a new [`InputNoteUpdate`] with the provided note with a `None` update type.
38    fn new_none(note: InputNoteRecord) -> Self {
39        Self { note, update_type: NoteUpdateType::None }
40    }
41
42    /// Creates a new [`InputNoteUpdate`] with the provided note with an `Insert` update type.
43    fn new_insert(note: InputNoteRecord) -> Self {
44        Self {
45            note,
46            update_type: NoteUpdateType::Insert,
47        }
48    }
49
50    /// Creates a new [`InputNoteUpdate`] with the provided note with an `Update` update type.
51    fn new_update(note: InputNoteRecord) -> Self {
52        Self {
53            note,
54            update_type: NoteUpdateType::Update,
55        }
56    }
57
58    /// Returns a reference the inner note record.
59    pub fn inner(&self) -> &InputNoteRecord {
60        &self.note
61    }
62
63    /// Returns a mutable reference to the inner note record. If the u
64    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    /// Returns the type of the note update.
74    pub fn update_type(&self) -> &NoteUpdateType {
75        &self.update_type
76    }
77
78    /// Returns the identifier of the inner note.
79    pub fn id(&self) -> NoteId {
80        self.note.id()
81    }
82}
83
84/// Represents the possible states of an output note record in a [`NoteUpdateTracker`].
85#[derive(Clone, Debug)]
86pub struct OutputNoteUpdate {
87    /// Output note being updated.
88    note: OutputNoteRecord,
89    /// Type of the note update.
90    update_type: NoteUpdateType,
91}
92
93impl OutputNoteUpdate {
94    /// Creates a new [`OutputNoteUpdate`] with the provided note with a `None` update type.
95    fn new_none(note: OutputNoteRecord) -> Self {
96        Self { note, update_type: NoteUpdateType::None }
97    }
98
99    /// Creates a new [`OutputNoteUpdate`] with the provided note with an `Insert` update type.
100    fn new_insert(note: OutputNoteRecord) -> Self {
101        Self {
102            note,
103            update_type: NoteUpdateType::Insert,
104        }
105    }
106
107    /// Creates a new [`OutputNoteUpdate`] with the provided note with an `Update` update type.
108    fn new_update(note: OutputNoteRecord) -> Self {
109        Self {
110            note,
111            update_type: NoteUpdateType::Update,
112        }
113    }
114
115    /// Returns a reference the inner note record.
116    pub fn inner(&self) -> &OutputNoteRecord {
117        &self.note
118    }
119
120    /// Returns a mutable reference to the inner note record. If the update type is `None` or
121    /// `Update`, it will be set to `Update`.
122    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    /// Returns the type of the note update.
132    pub fn update_type(&self) -> &NoteUpdateType {
133        &self.update_type
134    }
135
136    /// Returns the identifier of the inner note.
137    pub fn id(&self) -> NoteId {
138        self.note.id()
139    }
140}
141
142// NOTE UPDATE TRACKER
143// ================================================================================================
144
145/// Contains note changes to apply to the store.
146///
147/// This includes new notes that have been created and existing notes that have been updated. The
148/// tracker also lets state changes be applied to the contained notes, this allows for already
149/// updated notes to be further updated as new information is received.
150#[derive(Clone, Debug, Default)]
151pub struct NoteUpdateTracker {
152    /// A map of new and updated input note records to be upserted in the store.
153    input_notes: BTreeMap<NoteId, InputNoteUpdate>,
154    /// A map of updated output note records to be upserted in the store.
155    output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
156    /// Fast lookup map from nullifier to input note id.
157    input_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
158    /// Fast lookup map from nullifier to output note id.
159    output_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
160}
161
162impl NoteUpdateTracker {
163    /// Creates a [`NoteUpdateTracker`] with already-tracked notes.
164    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    /// Creates a [`NoteUpdateTracker`] for updates related to transactions.
180    ///
181    /// A transaction can:
182    ///
183    /// - Create input notes
184    /// - Update existing input notes (by consuming them)
185    /// - Create output notes
186    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    // GETTERS
209    // --------------------------------------------------------------------------------------------
210
211    /// Returns all input note records that have been updated.
212    ///
213    /// This may include:
214    /// - New notes that have been created that should be inserted.
215    /// - Existing tracked notes that should be updated.
216    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    /// Returns all output note records that have been updated.
223    ///
224    /// This may include:
225    /// - New notes that have been created that should be inserted.
226    /// - Existing tracked notes that should be updated.
227    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    /// Returns whether no new note-related information has been retrieved.
234    pub fn is_empty(&self) -> bool {
235        self.input_notes.is_empty() && self.output_notes.is_empty()
236    }
237
238    /// Returns input and output note unspent nullifiers.
239    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    // UPDATE METHODS
256    // --------------------------------------------------------------------------------------------
257
258    /// Inserts the new public note data into the tracker. This method doesn't check the relevance
259    /// of the note, so it should only be used for notes that are guaranteed to be relevant to the
260    /// client.
261    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    /// Applies the necessary state transitions to the [`NoteUpdateTracker`] when a note is
273    /// committed in a block and returns whether the committed note is tracked as input note.
274    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                // The note belongs to our locally tracked set of input notes
287                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            // The note belongs to our locally tracked set of output notes
298            output_note_record.inclusion_proof_received(inclusion_proof.clone())?;
299        }
300
301        Ok(is_tracked_as_input_note)
302    }
303
304    /// Applies the necessary state transitions to the [`NoteUpdateTracker`] when a note is
305    /// nullified in a block.
306    ///
307    /// For input note records two possible scenarios are considered:
308    /// 1. The note was being processed by a local transaction that just got committed.
309    /// 2. The note was consumed by an external transaction. If a local transaction was processing
310    ///    the note and it didn't get committed, the transaction should be discarded.
311    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                // The note was being processed by a local transaction that just got committed
323                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                // The note was consumed by an external transaction
331                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    // PRIVATE HELPERS
347    // --------------------------------------------------------------------------------------------
348
349    /// Returns a mutable reference to the input note record with the provided ID if it exists.
350    fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
351        self.input_notes.get_mut(&note_id).map(InputNoteUpdate::inner_mut)
352    }
353
354    /// Returns a mutable reference to the output note record with the provided ID if it exists.
355    fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
356        self.output_notes.get_mut(&note_id).map(OutputNoteUpdate::inner_mut)
357    }
358
359    /// Returns a mutable reference to the input note record with the provided nullifier if it
360    /// exists.
361    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(&note_id).map(InputNoteUpdate::inner_mut)
367    }
368
369    /// Returns a mutable reference to the output note record with the provided nullifier if it
370    /// exists.
371    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(&note_id).map(OutputNoteUpdate::inner_mut)
377    }
378
379    /// Insert an input note update
380    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    /// Insert an output note update
393    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}