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 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 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 output_note_record.inclusion_proof_received(inclusion_proof.clone())?;
260 }
261
262 Ok(())
263 }
264
265 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 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 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 fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
310 self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
311 }
312
313 fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
315 self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
316 }
317
318 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 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}