miden_client/note/
note_update_tracker.rs

1use alloc::collections::BTreeMap;
2
3use miden_objects::{
4    block::BlockHeader,
5    note::{NoteId, NoteInclusionProof, Nullifier},
6};
7
8use crate::{
9    ClientError,
10    rpc::domain::{note::CommittedNote, nullifier::NullifierUpdate},
11    store::{InputNoteRecord, OutputNoteRecord},
12    transaction::{TransactionRecord, TransactionStatus},
13};
14
15// NOTE UPDATE
16// ================================================================================================
17
18/// Represents the possible types of updates that can be applied to a note in a
19/// [`NoteUpdateTracker`].
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum NoteUpdateType {
22    /// Indicates that the note was already tracked but it was not updated.
23    None,
24    /// Indicates that the note is new and should be inserted in the store.
25    Insert,
26    /// Indicates that the note was already tracked and should be updated.
27    Update,
28}
29
30/// Represents the possible states of an input note record in a [`NoteUpdateTracker`].
31#[derive(Clone, Debug)]
32pub struct InputNoteUpdate {
33    /// Input note being updated.
34    note: InputNoteRecord,
35    /// Type of the note update.
36    update_type: NoteUpdateType,
37}
38
39impl InputNoteUpdate {
40    /// Creates a new [`InputNoteUpdate`] with the provided note with a `None` update type.
41    fn new_none(note: InputNoteRecord) -> Self {
42        Self { note, update_type: NoteUpdateType::None }
43    }
44
45    /// Creates a new [`InputNoteUpdate`] with the provided note with an `Insert` update type.
46    fn new_insert(note: InputNoteRecord) -> Self {
47        Self {
48            note,
49            update_type: NoteUpdateType::Insert,
50        }
51    }
52
53    /// Creates a new [`InputNoteUpdate`] with the provided note with an `Update` update type.
54    fn new_update(note: InputNoteRecord) -> Self {
55        Self {
56            note,
57            update_type: NoteUpdateType::Update,
58        }
59    }
60
61    /// Returns a reference the inner note record.
62    pub fn inner(&self) -> &InputNoteRecord {
63        &self.note
64    }
65
66    /// Returns a mutable reference to the inner note record. If the u
67    fn inner_mut(&mut self) -> &mut InputNoteRecord {
68        self.update_type = match self.update_type {
69            NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
70            NoteUpdateType::Insert => NoteUpdateType::Insert,
71        };
72
73        &mut self.note
74    }
75
76    /// Returns the type of the note update.
77    pub fn update_type(&self) -> &NoteUpdateType {
78        &self.update_type
79    }
80}
81
82/// Represents the possible states of an output note record in a [`NoteUpdateTracker`].
83#[derive(Clone, Debug)]
84pub struct OutputNoteUpdate {
85    /// Output note being updated.
86    note: OutputNoteRecord,
87    /// Type of the note update.
88    update_type: NoteUpdateType,
89}
90
91impl OutputNoteUpdate {
92    /// Creates a new [`OutputNoteUpdate`] with the provided note with a `None` update type.
93    fn new_none(note: OutputNoteRecord) -> Self {
94        Self { note, update_type: NoteUpdateType::None }
95    }
96
97    /// Creates a new [`OutputNoteUpdate`] with the provided note with an `Insert` update type.
98    fn new_insert(note: OutputNoteRecord) -> Self {
99        Self {
100            note,
101            update_type: NoteUpdateType::Insert,
102        }
103    }
104
105    /// Returns a reference the inner note record.
106    pub fn inner(&self) -> &OutputNoteRecord {
107        &self.note
108    }
109
110    /// Returns a mutable reference to the inner note record. If the update type is `None` or
111    /// `Update`, it will be set to `Update`.
112    fn inner_mut(&mut self) -> &mut OutputNoteRecord {
113        self.update_type = match self.update_type {
114            NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
115            NoteUpdateType::Insert => NoteUpdateType::Insert,
116        };
117
118        &mut self.note
119    }
120
121    /// Returns the type of the note update.
122    pub fn update_type(&self) -> &NoteUpdateType {
123        &self.update_type
124    }
125}
126
127// NOTE UPDATE TRACKER
128// ================================================================================================
129
130/// Contains note changes to apply to the store.
131///
132/// This includes new notes that have been created and existing notes that have been updated. The
133/// tracker also lets state changes be applied to the contained notes, this allows for already
134/// updated notes to be further updated as new information is received.
135#[derive(Clone, Debug, Default)]
136pub struct NoteUpdateTracker {
137    /// A map of new and updated input note records to be upserted in the store.
138    input_notes: BTreeMap<NoteId, InputNoteUpdate>,
139    /// A map of updated output note records to be upserted in the store.
140    output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
141}
142
143impl NoteUpdateTracker {
144    /// Creates a [`NoteUpdateTracker`] with already-tracked notes.
145    pub fn new(
146        input_notes: impl IntoIterator<Item = InputNoteRecord>,
147        output_notes: impl IntoIterator<Item = OutputNoteRecord>,
148    ) -> Self {
149        Self {
150            input_notes: input_notes
151                .into_iter()
152                .map(|note| (note.id(), InputNoteUpdate::new_none(note)))
153                .collect(),
154            output_notes: output_notes
155                .into_iter()
156                .map(|note| (note.id(), OutputNoteUpdate::new_none(note)))
157                .collect(),
158        }
159    }
160
161    /// Creates a [`NoteUpdateTracker`] for updates related to transactions.
162    ///
163    /// A transaction can:
164    ///
165    /// - Create input notes
166    /// - Update existing input notes (by consuming them)
167    /// - Create output notes
168    pub fn for_transaction_updates(
169        new_input_notes: impl IntoIterator<Item = InputNoteRecord>,
170        updated_input_notes: impl IntoIterator<Item = InputNoteRecord>,
171        new_output_notes: impl IntoIterator<Item = OutputNoteRecord>,
172    ) -> Self {
173        Self {
174            input_notes: new_input_notes
175                .into_iter()
176                .map(|note| (note.id(), InputNoteUpdate::new_insert(note)))
177                .chain(
178                    updated_input_notes
179                        .into_iter()
180                        .map(|note| (note.id(), InputNoteUpdate::new_update(note))),
181                )
182                .collect(),
183            output_notes: new_output_notes
184                .into_iter()
185                .map(|note| (note.id(), OutputNoteUpdate::new_insert(note)))
186                .collect(),
187        }
188    }
189
190    // GETTERS
191    // --------------------------------------------------------------------------------------------
192
193    /// Returns all input note records that have been updated.
194    ///
195    /// This may include:
196    /// - New notes that have been created that should be inserted.
197    /// - Existing tracked notes that should be updated.
198    pub fn updated_input_notes(&self) -> impl Iterator<Item = &InputNoteUpdate> {
199        self.input_notes.values().filter(|note| {
200            matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
201        })
202    }
203
204    /// Returns all output note records that have been updated.
205    ///
206    /// This may include:
207    /// - New notes that have been created that should be inserted.
208    /// - Existing tracked notes that should be updated.
209    pub fn updated_output_notes(&self) -> impl Iterator<Item = &OutputNoteUpdate> {
210        self.output_notes.values().filter(|note| {
211            matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
212        })
213    }
214
215    /// Returns whether no new note-related information has been retrieved.
216    pub fn is_empty(&self) -> bool {
217        self.input_notes.is_empty() && self.output_notes.is_empty()
218    }
219
220    pub fn unspent_nullifiers(&self) -> impl Iterator<Item = Nullifier> + '_ {
221        self.input_notes
222            .values()
223            .filter(|note| !note.inner().is_consumed())
224            .map(|note| note.inner().nullifier())
225    }
226
227    // UPDATE METHODS
228    // --------------------------------------------------------------------------------------------
229
230    /// Applies the necessary state transitions to the [`NoteUpdateTracker`] when a note is
231    /// committed in a block.
232    pub(crate) fn apply_committed_note_state_transitions(
233        &mut self,
234        committed_note: &CommittedNote,
235        public_note_data: Option<InputNoteRecord>,
236        block_header: &BlockHeader,
237    ) -> Result<(), ClientError> {
238        let inclusion_proof = NoteInclusionProof::new(
239            block_header.block_num(),
240            committed_note.note_index(),
241            committed_note.merkle_path().clone(),
242        )?;
243
244        if let Some(mut input_note_record) = public_note_data {
245            input_note_record.block_header_received(block_header)?;
246            self.input_notes
247                .insert(input_note_record.id(), InputNoteUpdate::new_insert(input_note_record));
248        }
249
250        if let Some(input_note_record) = self.get_input_note_by_id(*committed_note.note_id()) {
251            // The note belongs to our locally tracked set of input notes
252            input_note_record
253                .inclusion_proof_received(inclusion_proof.clone(), committed_note.metadata())?;
254            input_note_record.block_header_received(block_header)?;
255        }
256
257        if let Some(output_note_record) = self.get_output_note_by_id(*committed_note.note_id()) {
258            // The note belongs to our locally tracked set of output notes
259            output_note_record.inclusion_proof_received(inclusion_proof.clone())?;
260        }
261
262        Ok(())
263    }
264
265    /// Applies the necessary state transitions to the [`NoteUpdateTracker`] when a note is
266    /// nullified in a block.
267    ///
268    /// For input note records two possible scenarios are considered:
269    /// 1. The note was being processed by a local transaction that just got committed.
270    /// 2. The note was consumed by an external transaction. If a local transaction was processing
271    ///    the note and it didn't get committed, the transaction should be discarded.
272    pub(crate) fn apply_nullifiers_state_transitions<'a>(
273        &mut self,
274        nullifier_update: &NullifierUpdate,
275        mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
276    ) -> Result<(), ClientError> {
277        if let Some(input_note_record) =
278            self.get_input_note_by_nullifier(nullifier_update.nullifier)
279        {
280            if let Some(consumer_transaction) = committed_transactions
281                .find(|t| input_note_record.consumer_transaction_id() == Some(&t.id))
282            {
283                // The note was being processed by a local transaction that just got committed
284                if let TransactionStatus::Committed(commit_height) = consumer_transaction.status {
285                    input_note_record
286                        .transaction_committed(consumer_transaction.id, commit_height.as_u32())?;
287                }
288            } else {
289                // The note was consumed by an external transaction
290                input_note_record
291                    .consumed_externally(nullifier_update.nullifier, nullifier_update.block_num)?;
292            }
293        }
294
295        if let Some(output_note_record) =
296            self.get_output_note_by_nullifier(nullifier_update.nullifier)
297        {
298            output_note_record
299                .nullifier_received(nullifier_update.nullifier, nullifier_update.block_num)?;
300        }
301
302        Ok(())
303    }
304
305    // PRIVATE HELPERS
306    // --------------------------------------------------------------------------------------------
307
308    /// Returns a mutable reference to the input note record with the provided ID if it exists.
309    fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
310        self.input_notes.get_mut(&note_id).map(InputNoteUpdate::inner_mut)
311    }
312
313    /// Returns a mutable reference to the output note record with the provided ID if it exists.
314    fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
315        self.output_notes.get_mut(&note_id).map(OutputNoteUpdate::inner_mut)
316    }
317
318    /// Returns a mutable reference to the input note record with the provided nullifier if it
319    /// exists.
320    fn get_input_note_by_nullifier(
321        &mut self,
322        nullifier: Nullifier,
323    ) -> Option<&mut InputNoteRecord> {
324        self.input_notes
325            .values_mut()
326            .find(|note| note.inner().nullifier() == nullifier)
327            .map(InputNoteUpdate::inner_mut)
328    }
329
330    /// Returns a mutable reference to the output note record with the provided nullifier if it
331    /// exists.
332    fn get_output_note_by_nullifier(
333        &mut self,
334        nullifier: Nullifier,
335    ) -> Option<&mut OutputNoteRecord> {
336        self.output_notes
337            .values_mut()
338            .find(|note| note.inner().nullifier() == Some(nullifier))
339            .map(OutputNoteUpdate::inner_mut)
340    }
341}