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
79/// Represents the possible states of an output note record in a [`NoteUpdateTracker`].
80#[derive(Clone, Debug)]
81pub struct OutputNoteUpdate {
82    /// Output note being updated.
83    note: OutputNoteRecord,
84    /// Type of the note update.
85    update_type: NoteUpdateType,
86}
87
88impl OutputNoteUpdate {
89    /// Creates a new [`OutputNoteUpdate`] with the provided note with a `None` update type.
90    fn new_none(note: OutputNoteRecord) -> Self {
91        Self { note, update_type: NoteUpdateType::None }
92    }
93
94    /// Creates a new [`OutputNoteUpdate`] with the provided note with an `Insert` update type.
95    fn new_insert(note: OutputNoteRecord) -> Self {
96        Self {
97            note,
98            update_type: NoteUpdateType::Insert,
99        }
100    }
101
102    /// Creates a new [`OutputNoteUpdate`] with the provided note with an `Update` update type.
103    fn new_update(note: OutputNoteRecord) -> Self {
104        Self {
105            note,
106            update_type: NoteUpdateType::Update,
107        }
108    }
109
110    /// Returns a reference the inner note record.
111    pub fn inner(&self) -> &OutputNoteRecord {
112        &self.note
113    }
114
115    /// Returns a mutable reference to the inner note record. If the update type is `None` or
116    /// `Update`, it will be set to `Update`.
117    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    /// Returns the type of the note update.
127    pub fn update_type(&self) -> &NoteUpdateType {
128        &self.update_type
129    }
130}
131
132// NOTE UPDATE TRACKER
133// ================================================================================================
134
135/// Contains note changes to apply to the store.
136///
137/// This includes new notes that have been created and existing notes that have been updated. The
138/// tracker also lets state changes be applied to the contained notes, this allows for already
139/// updated notes to be further updated as new information is received.
140#[derive(Clone, Debug, Default)]
141pub struct NoteUpdateTracker {
142    /// A map of new and updated input note records to be upserted in the store.
143    input_notes: BTreeMap<NoteId, InputNoteUpdate>,
144    /// A map of updated output note records to be upserted in the store.
145    output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
146    /// Fast lookup map from nullifier to input note id.
147    input_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
148    /// Fast lookup map from nullifier to output note id.
149    output_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
150}
151
152impl NoteUpdateTracker {
153    /// Creates a [`NoteUpdateTracker`] with already-tracked notes.
154    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    /// Creates a [`NoteUpdateTracker`] for updates related to transactions.
170    ///
171    /// A transaction can:
172    ///
173    /// - Create input notes
174    /// - Update existing input notes (by consuming them)
175    /// - Create output notes
176    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    // GETTERS
199    // --------------------------------------------------------------------------------------------
200
201    /// Returns all input note records that have been updated.
202    ///
203    /// This may include:
204    /// - New notes that have been created that should be inserted.
205    /// - Existing tracked notes that should be updated.
206    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    /// Returns all output note records that have been updated.
213    ///
214    /// This may include:
215    /// - New notes that have been created that should be inserted.
216    /// - Existing tracked notes that should be updated.
217    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    /// Returns whether no new note-related information has been retrieved.
224    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    // UPDATE METHODS
236    // --------------------------------------------------------------------------------------------
237
238    /// Inserts the new public note data into the tracker. This method doesn't check the relevance
239    /// of the note, so it should only be used for notes that are guaranteed to be relevant to the
240    /// client.
241    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    /// Applies the necessary state transitions to the [`NoteUpdateTracker`] when a note is
253    /// committed in a block and returns whether the committed note is tracked as input note.
254    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                // The note belongs to our locally tracked set of input notes
267                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            // The note belongs to our locally tracked set of output notes
278            output_note_record.inclusion_proof_received(inclusion_proof.clone())?;
279        }
280
281        Ok(is_tracked_as_input_note)
282    }
283
284    /// Applies the necessary state transitions to the [`NoteUpdateTracker`] when a note is
285    /// nullified in a block.
286    ///
287    /// For input note records two possible scenarios are considered:
288    /// 1. The note was being processed by a local transaction that just got committed.
289    /// 2. The note was consumed by an external transaction. If a local transaction was processing
290    ///    the note and it didn't get committed, the transaction should be discarded.
291    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                // The note was being processed by a local transaction that just got committed
303                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                // The note was consumed by an external transaction
311                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    // PRIVATE HELPERS
327    // --------------------------------------------------------------------------------------------
328
329    /// Returns a mutable reference to the input note record with the provided ID if it exists.
330    fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
331        self.input_notes.get_mut(&note_id).map(InputNoteUpdate::inner_mut)
332    }
333
334    /// Returns a mutable reference to the output note record with the provided ID if it exists.
335    fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
336        self.output_notes.get_mut(&note_id).map(OutputNoteUpdate::inner_mut)
337    }
338
339    /// Returns a mutable reference to the input note record with the provided nullifier if it
340    /// exists.
341    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(&note_id).map(InputNoteUpdate::inner_mut)
347    }
348
349    /// Returns a mutable reference to the output note record with the provided nullifier if it
350    /// exists.
351    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(&note_id).map(OutputNoteUpdate::inner_mut)
357    }
358
359    /// Insert an input note update
360    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    /// Insert an output note update
373    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}