miden_client/note/
note_update_tracker.rs1use 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#[derive(Clone, Debug, PartialEq, Eq)]
18pub enum NoteUpdateType {
19 None,
21 Insert,
23 Update,
25}
26
27#[derive(Clone, Debug)]
29pub struct InputNoteUpdate {
30 note: InputNoteRecord,
32 update_type: NoteUpdateType,
34}
35
36impl InputNoteUpdate {
37 fn new_none(note: InputNoteRecord) -> Self {
39 Self { note, update_type: NoteUpdateType::None }
40 }
41
42 fn new_insert(note: InputNoteRecord) -> Self {
44 Self {
45 note,
46 update_type: NoteUpdateType::Insert,
47 }
48 }
49
50 fn new_update(note: InputNoteRecord) -> Self {
52 Self {
53 note,
54 update_type: NoteUpdateType::Update,
55 }
56 }
57
58 pub fn inner(&self) -> &InputNoteRecord {
60 &self.note
61 }
62
63 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 pub fn update_type(&self) -> &NoteUpdateType {
75 &self.update_type
76 }
77}
78
79#[derive(Clone, Debug)]
81pub struct OutputNoteUpdate {
82 note: OutputNoteRecord,
84 update_type: NoteUpdateType,
86}
87
88impl OutputNoteUpdate {
89 fn new_none(note: OutputNoteRecord) -> Self {
91 Self { note, update_type: NoteUpdateType::None }
92 }
93
94 fn new_insert(note: OutputNoteRecord) -> Self {
96 Self {
97 note,
98 update_type: NoteUpdateType::Insert,
99 }
100 }
101
102 pub fn inner(&self) -> &OutputNoteRecord {
104 &self.note
105 }
106
107 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 pub fn update_type(&self) -> &NoteUpdateType {
120 &self.update_type
121 }
122}
123
124#[derive(Clone, Debug, Default)]
133pub struct NoteUpdateTracker {
134 input_notes: BTreeMap<NoteId, InputNoteUpdate>,
136 output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
138}
139
140impl NoteUpdateTracker {
141 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 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 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 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 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 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 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 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 output_note_record.inclusion_proof_received(inclusion_proof.clone())?;
265 }
266
267 Ok(())
268 }
269
270 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 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 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 fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
317 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
318 }
319
320 fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
322 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
323 }
324
325 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 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}