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.
254    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            // 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
272        if let Some(output_note_record) = self.get_output_note_by_id(*committed_note.note_id()) {
273            // The note belongs to our locally tracked set of output notes
274            output_note_record.inclusion_proof_received(inclusion_proof.clone())?;
275        }
276
277        Ok(())
278    }
279
280    /// Applies the necessary state transitions to the [`NoteUpdateTracker`] when a note is
281    /// nullified in a block.
282    ///
283    /// For input note records two possible scenarios are considered:
284    /// 1. The note was being processed by a local transaction that just got committed.
285    /// 2. The note was consumed by an external transaction. If a local transaction was processing
286    ///    the note and it didn't get committed, the transaction should be discarded.
287    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                // The note was being processed by a local transaction that just got committed
299                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                // The note was consumed by an external transaction
307                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    // PRIVATE HELPERS
323    // --------------------------------------------------------------------------------------------
324
325    /// Returns a mutable reference to the input note record with the provided ID if it exists.
326    fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
327        self.input_notes.get_mut(&note_id).map(InputNoteUpdate::inner_mut)
328    }
329
330    /// Returns a mutable reference to the output note record with the provided ID if it exists.
331    fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
332        self.output_notes.get_mut(&note_id).map(OutputNoteUpdate::inner_mut)
333    }
334
335    /// Returns a mutable reference to the input note record with the provided nullifier if it
336    /// exists.
337    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(&note_id).map(InputNoteUpdate::inner_mut)
343    }
344
345    /// Returns a mutable reference to the output note record with the provided nullifier if it
346    /// exists.
347    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(&note_id).map(OutputNoteUpdate::inner_mut)
353    }
354
355    /// Insert an input note update
356    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    /// Insert an output note update
369    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}