miden_client/note/
note_update_tracker.rs1use 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#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum NoteUpdateType {
22 None,
24 Insert,
26 Update,
28}
29
30#[derive(Clone, Debug)]
32pub struct InputNoteUpdate {
33 note: InputNoteRecord,
35 update_type: NoteUpdateType,
37}
38
39impl InputNoteUpdate {
40 fn new_none(note: InputNoteRecord) -> Self {
42 Self { note, update_type: NoteUpdateType::None }
43 }
44
45 fn new_insert(note: InputNoteRecord) -> Self {
47 Self {
48 note,
49 update_type: NoteUpdateType::Insert,
50 }
51 }
52
53 fn new_update(note: InputNoteRecord) -> Self {
55 Self {
56 note,
57 update_type: NoteUpdateType::Update,
58 }
59 }
60
61 pub fn inner(&self) -> &InputNoteRecord {
63 &self.note
64 }
65
66 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 pub fn update_type(&self) -> &NoteUpdateType {
78 &self.update_type
79 }
80}
81
82#[derive(Clone, Debug)]
84pub struct OutputNoteUpdate {
85 note: OutputNoteRecord,
87 update_type: NoteUpdateType,
89}
90
91impl OutputNoteUpdate {
92 fn new_none(note: OutputNoteRecord) -> Self {
94 Self { note, update_type: NoteUpdateType::None }
95 }
96
97 fn new_insert(note: OutputNoteRecord) -> Self {
99 Self {
100 note,
101 update_type: NoteUpdateType::Insert,
102 }
103 }
104
105 pub fn inner(&self) -> &OutputNoteRecord {
107 &self.note
108 }
109
110 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 pub fn update_type(&self) -> &NoteUpdateType {
123 &self.update_type
124 }
125}
126
127#[derive(Clone, Debug, Default)]
136pub struct NoteUpdateTracker {
137 input_notes: BTreeMap<NoteId, InputNoteUpdate>,
139 output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
141}
142
143impl NoteUpdateTracker {
144 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 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 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 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 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 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 let mut tracked_input = false;
245
246 if let Some(input_note_record) = self.get_input_note_by_id(*committed_note.note_id()) {
247 input_note_record
249 .inclusion_proof_received(inclusion_proof.clone(), committed_note.metadata())?;
250 input_note_record.block_header_received(block_header)?;
251 tracked_input = true;
252 }
253
254 if let Some(output_note_record) = self.get_output_note_by_id(*committed_note.note_id()) {
255 output_note_record.inclusion_proof_received(inclusion_proof.clone())?;
257
258 if !tracked_input {
259 return Ok(());
262 }
263 }
264
265 if let Some(mut input_note_record) = public_note_data {
266 input_note_record.block_header_received(block_header)?;
267 self.input_notes
268 .insert(input_note_record.id(), InputNoteUpdate::new_insert(input_note_record));
269 }
270
271 Ok(())
272 }
273
274 pub(crate) fn apply_nullifiers_state_transitions<'a>(
282 &mut self,
283 nullifier_update: &NullifierUpdate,
284 mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
285 ) -> Result<(), ClientError> {
286 if let Some(input_note_record) =
287 self.get_input_note_by_nullifier(nullifier_update.nullifier)
288 {
289 if let Some(consumer_transaction) = committed_transactions
290 .find(|t| input_note_record.consumer_transaction_id() == Some(&t.id))
291 {
292 if let TransactionStatus::Committed(commit_height) = consumer_transaction.status {
294 input_note_record
295 .transaction_committed(consumer_transaction.id, commit_height.as_u32())?;
296 }
297 } else {
298 input_note_record
300 .consumed_externally(nullifier_update.nullifier, nullifier_update.block_num)?;
301 }
302 }
303
304 if let Some(output_note_record) =
305 self.get_output_note_by_nullifier(nullifier_update.nullifier)
306 {
307 output_note_record
308 .nullifier_received(nullifier_update.nullifier, nullifier_update.block_num)?;
309 }
310
311 Ok(())
312 }
313
314 fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
319 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
320 }
321
322 fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
324 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
325 }
326
327 fn get_input_note_by_nullifier(
330 &mut self,
331 nullifier: Nullifier,
332 ) -> Option<&mut InputNoteRecord> {
333 self.input_notes
334 .values_mut()
335 .find(|note| note.inner().nullifier() == nullifier)
336 .map(InputNoteUpdate::inner_mut)
337 }
338
339 fn get_output_note_by_nullifier(
342 &mut self,
343 nullifier: Nullifier,
344 ) -> Option<&mut OutputNoteRecord> {
345 self.output_notes
346 .values_mut()
347 .find(|note| note.inner().nullifier() == Some(nullifier))
348 .map(OutputNoteUpdate::inner_mut)
349 }
350}