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, 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    /// Returns a reference the inner note record.
103    pub fn inner(&self) -> &OutputNoteRecord {
104        &self.note
105    }
106
107    /// Returns a mutable reference to the inner note record. If the update type is `None` or
108    /// `Update`, it will be set to `Update`.
109    fn inner_mut(&mut self) -> &mut OutputNoteRecord {
110        self.update_type = match self.update_type {
111            NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
112            NoteUpdateType::Insert => NoteUpdateType::Insert,
113        };
114
115        &mut self.note
116    }
117
118    /// Returns the type of the note update.
119    pub fn update_type(&self) -> &NoteUpdateType {
120        &self.update_type
121    }
122}
123
124// NOTE UPDATE TRACKER
125// ================================================================================================
126
127/// Contains note changes to apply to the store.
128///
129/// This includes new notes that have been created and existing notes that have been updated. The
130/// tracker also lets state changes be applied to the contained notes, this allows for already
131/// updated notes to be further updated as new information is received.
132#[derive(Clone, Debug, Default)]
133pub struct NoteUpdateTracker {
134    /// A map of new and updated input note records to be upserted in the store.
135    input_notes: BTreeMap<NoteId, InputNoteUpdate>,
136    /// A map of updated output note records to be upserted in the store.
137    output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
138}
139
140impl NoteUpdateTracker {
141    /// Creates a [`NoteUpdateTracker`] with already-tracked notes.
142    pub fn new(
143        input_notes: impl IntoIterator<Item = InputNoteRecord>,
144        output_notes: impl IntoIterator<Item = OutputNoteRecord>,
145    ) -> Self {
146        Self {
147            input_notes: input_notes
148                .into_iter()
149                .map(|note| (note.id(), InputNoteUpdate::new_none(note)))
150                .collect(),
151            output_notes: output_notes
152                .into_iter()
153                .map(|note| (note.id(), OutputNoteUpdate::new_none(note)))
154                .collect(),
155        }
156    }
157
158    /// Creates a [`NoteUpdateTracker`] for updates related to transactions.
159    ///
160    /// A transaction can:
161    ///
162    /// - Create input notes
163    /// - Update existing input notes (by consuming them)
164    /// - Create output notes
165    pub fn for_transaction_updates(
166        new_input_notes: impl IntoIterator<Item = InputNoteRecord>,
167        updated_input_notes: impl IntoIterator<Item = InputNoteRecord>,
168        new_output_notes: impl IntoIterator<Item = OutputNoteRecord>,
169    ) -> Self {
170        Self {
171            input_notes: new_input_notes
172                .into_iter()
173                .map(|note| (note.id(), InputNoteUpdate::new_insert(note)))
174                .chain(
175                    updated_input_notes
176                        .into_iter()
177                        .map(|note| (note.id(), InputNoteUpdate::new_update(note))),
178                )
179                .collect(),
180            output_notes: new_output_notes
181                .into_iter()
182                .map(|note| (note.id(), OutputNoteUpdate::new_insert(note)))
183                .collect(),
184        }
185    }
186
187    // GETTERS
188    // --------------------------------------------------------------------------------------------
189
190    /// Returns all input note records that have been updated.
191    ///
192    /// This may include:
193    /// - New notes that have been created that should be inserted.
194    /// - Existing tracked notes that should be updated.
195    pub fn updated_input_notes(&self) -> impl Iterator<Item = &InputNoteUpdate> {
196        self.input_notes.values().filter(|note| {
197            matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
198        })
199    }
200
201    /// Returns all output 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_output_notes(&self) -> impl Iterator<Item = &OutputNoteUpdate> {
207        self.output_notes.values().filter(|note| {
208            matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
209        })
210    }
211
212    /// Returns whether no new note-related information has been retrieved.
213    pub fn is_empty(&self) -> bool {
214        self.input_notes.is_empty() && self.output_notes.is_empty()
215    }
216
217    pub fn unspent_nullifiers(&self) -> impl Iterator<Item = Nullifier> + '_ {
218        self.input_notes
219            .values()
220            .filter(|note| !note.inner().is_consumed())
221            .map(|note| note.inner().nullifier())
222    }
223
224    // UPDATE METHODS
225    // --------------------------------------------------------------------------------------------
226
227    /// Inserts the new public note data into the tracker. This method doesn't check the relevance
228    /// of the note, so it should only be used for notes that are guaranteed to be relevant to the
229    /// client.
230    pub(crate) fn apply_new_public_note(
231        &mut self,
232        mut public_note_data: InputNoteRecord,
233        block_header: &BlockHeader,
234    ) -> Result<(), ClientError> {
235        public_note_data.block_header_received(block_header)?;
236        self.input_notes
237            .insert(public_note_data.id(), InputNoteUpdate::new_insert(public_note_data));
238
239        Ok(())
240    }
241
242    /// Applies the necessary state transitions to the [`NoteUpdateTracker`] when a note is
243    /// committed in a block.
244    pub(crate) fn apply_committed_note_state_transitions(
245        &mut self,
246        committed_note: &CommittedNote,
247        block_header: &BlockHeader,
248    ) -> Result<(), ClientError> {
249        let inclusion_proof = NoteInclusionProof::new(
250            block_header.block_num(),
251            committed_note.note_index(),
252            committed_note.inclusion_path().clone(),
253        )?;
254
255        if let Some(input_note_record) = self.get_input_note_by_id(*committed_note.note_id()) {
256            // The note belongs to our locally tracked set of input notes
257            input_note_record
258                .inclusion_proof_received(inclusion_proof.clone(), committed_note.metadata())?;
259            input_note_record.block_header_received(block_header)?;
260        }
261
262        if let Some(output_note_record) = self.get_output_note_by_id(*committed_note.note_id()) {
263            // The note belongs to our locally tracked set of output notes
264            output_note_record.inclusion_proof_received(inclusion_proof.clone())?;
265        }
266
267        Ok(())
268    }
269
270    /// Applies the necessary state transitions to the [`NoteUpdateTracker`] when a note is
271    /// nullified in a block.
272    ///
273    /// For input note records two possible scenarios are considered:
274    /// 1. The note was being processed by a local transaction that just got committed.
275    /// 2. The note was consumed by an external transaction. If a local transaction was processing
276    ///    the note and it didn't get committed, the transaction should be discarded.
277    pub(crate) fn apply_nullifiers_state_transitions<'a>(
278        &mut self,
279        nullifier_update: &NullifierUpdate,
280        mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
281    ) -> Result<(), ClientError> {
282        if let Some(input_note_record) =
283            self.get_input_note_by_nullifier(nullifier_update.nullifier)
284        {
285            if let Some(consumer_transaction) = committed_transactions
286                .find(|t| input_note_record.consumer_transaction_id() == Some(&t.id))
287            {
288                // The note was being processed by a local transaction that just got committed
289                if let TransactionStatus::Committed { block_number, .. } =
290                    consumer_transaction.status
291                {
292                    input_note_record
293                        .transaction_committed(consumer_transaction.id, block_number.as_u32())?;
294                }
295            } else {
296                // The note was consumed by an external transaction
297                input_note_record
298                    .consumed_externally(nullifier_update.nullifier, nullifier_update.block_num)?;
299            }
300        }
301
302        if let Some(output_note_record) =
303            self.get_output_note_by_nullifier(nullifier_update.nullifier)
304        {
305            output_note_record
306                .nullifier_received(nullifier_update.nullifier, nullifier_update.block_num)?;
307        }
308
309        Ok(())
310    }
311
312    // PRIVATE HELPERS
313    // --------------------------------------------------------------------------------------------
314
315    /// Returns a mutable reference to the input note record with the provided ID if it exists.
316    fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
317        self.input_notes.get_mut(&note_id).map(InputNoteUpdate::inner_mut)
318    }
319
320    /// Returns a mutable reference to the output note record with the provided ID if it exists.
321    fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
322        self.output_notes.get_mut(&note_id).map(OutputNoteUpdate::inner_mut)
323    }
324
325    /// Returns a mutable reference to the input note record with the provided nullifier if it
326    /// exists.
327    fn get_input_note_by_nullifier(
328        &mut self,
329        nullifier: Nullifier,
330    ) -> Option<&mut InputNoteRecord> {
331        self.input_notes
332            .values_mut()
333            .find(|note| note.inner().nullifier() == nullifier)
334            .map(InputNoteUpdate::inner_mut)
335    }
336
337    /// Returns a mutable reference to the output note record with the provided nullifier if it
338    /// exists.
339    fn get_output_note_by_nullifier(
340        &mut self,
341        nullifier: Nullifier,
342    ) -> Option<&mut OutputNoteRecord> {
343        self.output_notes
344            .values_mut()
345            .find(|note| note.inner().nullifier() == Some(nullifier))
346            .map(OutputNoteUpdate::inner_mut)
347    }
348}