1use alloc::string::ToString;
12
13use miden_objects::{
14 block::BlockNumber,
15 note::{Note, NoteDetails, NoteFile, NoteId, NoteInclusionProof, NoteMetadata, NoteTag},
16};
17
18use crate::{
19 Client, ClientError,
20 rpc::{RpcError, domain::note::FetchedNote},
21 store::{InputNoteRecord, InputNoteState, input_note_states::ExpectedNoteState},
22 sync::NoteTagRecord,
23};
24
25impl Client {
27 pub async fn import_note(&mut self, note_file: NoteFile) -> Result<NoteId, ClientError> {
46 let id = match ¬e_file {
47 NoteFile::NoteId(id) => *id,
48 NoteFile::NoteDetails { details, .. } => details.id(),
49 NoteFile::NoteWithProof(note, _) => note.id(),
50 };
51
52 let previous_note = self.get_input_note(id).await?;
53
54 if let Some(true) = previous_note.as_ref().map(InputNoteRecord::is_processing) {
56 return Err(ClientError::NoteImportError(format!(
57 "Can't overwrite note with id {id} as it's currently being processed",
58 )));
59 }
60
61 let note = match note_file {
62 NoteFile::NoteId(id) => self.import_note_record_by_id(previous_note, id).await?,
63 NoteFile::NoteDetails { details, after_block_num, tag } => {
64 self.import_note_record_by_details(previous_note, details, after_block_num, tag)
65 .await?
66 },
67 NoteFile::NoteWithProof(note, inclusion_proof) => {
68 self.import_note_record_by_proof(previous_note, note, inclusion_proof).await?
69 },
70 };
71
72 if let Some(note) = note {
73 if let InputNoteState::Expected(ExpectedNoteState { tag: Some(tag), .. }) = note.state()
74 {
75 self.store
76 .add_note_tag(NoteTagRecord::with_note_source(*tag, note.id()))
77 .await?;
78 }
79 self.store.upsert_input_notes(&[note]).await?;
80 }
81
82 Ok(id)
83 }
84
85 async fn import_note_record_by_id(
96 &self,
97 previous_note: Option<InputNoteRecord>,
98 id: NoteId,
99 ) -> Result<Option<InputNoteRecord>, ClientError> {
100 let fetched_note = self.rpc_api.get_note_by_id(id).await.map_err(|err| match err {
101 RpcError::NoteNotFound(note_id) => ClientError::NoteNotFoundOnChain(note_id),
102 err => ClientError::RpcError(err),
103 })?;
104
105 let inclusion_proof = fetched_note.inclusion_proof().clone();
106
107 if let Some(mut previous_note) = previous_note {
108 if previous_note.inclusion_proof_received(inclusion_proof, *fetched_note.metadata())? {
109 self.store.remove_note_tag((&previous_note).try_into()?).await?;
110
111 Ok(Some(previous_note))
112 } else {
113 Ok(None)
114 }
115 } else {
116 let fetched_note = match fetched_note {
117 FetchedNote::Public(note, _) => note,
118 FetchedNote::Private(..) => {
119 return Err(ClientError::NoteImportError(
120 "Incomplete imported note is private".to_string(),
121 ));
122 },
123 };
124
125 self.import_note_record_by_proof(previous_note, fetched_note, inclusion_proof)
126 .await
127 }
128 }
129
130 async fn import_note_record_by_proof(
138 &self,
139 previous_note: Option<InputNoteRecord>,
140 note: Note,
141 inclusion_proof: NoteInclusionProof,
142 ) -> Result<Option<InputNoteRecord>, ClientError> {
143 let metadata = *note.metadata();
144 let mut note_record = previous_note.unwrap_or(InputNoteRecord::new(
145 note.into(),
146 self.store.get_current_timestamp(),
147 ExpectedNoteState {
148 metadata: Some(metadata),
149 after_block_num: inclusion_proof.location().block_num(),
150 tag: Some(metadata.tag()),
151 }
152 .into(),
153 ));
154
155 if let Some(block_height) = self
156 .rpc_api
157 .get_nullifier_commit_height(
158 ¬e_record.nullifier(),
159 inclusion_proof.location().block_num(),
160 )
161 .await?
162 {
163 if note_record.consumed_externally(note_record.nullifier(), block_height)? {
164 return Ok(Some(note_record));
165 }
166
167 Ok(None)
168 } else {
169 let block_height = inclusion_proof.location().block_num();
170 let current_block_num = self.get_sync_height().await?;
171
172 let mut note_changed =
173 note_record.inclusion_proof_received(inclusion_proof, metadata)?;
174
175 if block_height < current_block_num {
176 let mut current_partial_mmr = self.build_current_partial_mmr().await?;
179
180 let block_header = self
181 .get_and_store_authenticated_block(block_height, &mut current_partial_mmr)
182 .await?;
183
184 note_changed |= note_record.block_header_received(&block_header)?;
185 } else {
186 self.store
189 .add_note_tag(NoteTagRecord::with_note_source(metadata.tag(), note_record.id()))
190 .await?;
191 }
192
193 if note_changed { Ok(Some(note_record)) } else { Ok(None) }
194 }
195 }
196
197 async fn import_note_record_by_details(
200 &mut self,
201 previous_note: Option<InputNoteRecord>,
202 details: NoteDetails,
203 after_block_num: BlockNumber,
204 tag: Option<NoteTag>,
205 ) -> Result<Option<InputNoteRecord>, ClientError> {
206 let mut note_record = previous_note.unwrap_or({
207 InputNoteRecord::new(
208 details,
209 self.store.get_current_timestamp(),
210 ExpectedNoteState { metadata: None, after_block_num, tag }.into(),
211 )
212 });
213
214 let committed_note_data = if let Some(tag) = tag {
215 self.check_expected_note(after_block_num, tag, note_record.details()).await?
216 } else {
217 None
218 };
219
220 match committed_note_data {
221 Some((metadata, inclusion_proof)) => {
222 let mut current_partial_mmr = self.build_current_partial_mmr().await?;
223 let block_header = self
224 .get_and_store_authenticated_block(
225 inclusion_proof.location().block_num(),
226 &mut current_partial_mmr,
227 )
228 .await?;
229
230 let note_changed =
231 note_record.inclusion_proof_received(inclusion_proof, metadata)?;
232
233 if note_record.block_header_received(&block_header)? | note_changed {
234 self.store
235 .remove_note_tag(NoteTagRecord::with_note_source(
236 metadata.tag(),
237 note_record.id(),
238 ))
239 .await?;
240
241 Ok(Some(note_record))
242 } else {
243 Ok(None)
244 }
245 },
246 None => Ok(Some(note_record)),
247 }
248 }
249
250 async fn check_expected_note(
254 &mut self,
255 mut request_block_num: BlockNumber,
256 tag: NoteTag,
257 expected_note: &miden_objects::note::NoteDetails,
258 ) -> Result<Option<(NoteMetadata, NoteInclusionProof)>, ClientError> {
259 let current_block_num = self.get_sync_height().await?;
260 loop {
261 if request_block_num > current_block_num {
262 return Ok(None);
263 }
264
265 let sync_notes = self.rpc_api.sync_notes(request_block_num, &[tag]).await?;
266
267 if sync_notes.block_header.block_num() == sync_notes.chain_tip.into() {
268 return Ok(None);
269 }
270
271 let committed_note =
274 sync_notes.notes.iter().find(|note| note.note_id() == &expected_note.id());
275
276 if let Some(note) = committed_note {
277 let note_block_num = sync_notes.block_header.block_num();
280
281 if note_block_num > current_block_num {
282 return Ok(None);
283 }
284
285 let note_inclusion_proof = NoteInclusionProof::new(
286 note_block_num,
287 note.note_index(),
288 note.merkle_path().clone(),
289 )?;
290
291 return Ok(Some((note.metadata(), note_inclusion_proof)));
292 }
293 request_block_num = sync_notes.block_header.block_num();
298 }
299 }
300}