1use alloc::collections::BTreeMap;
2use alloc::vec::Vec;
3
4use miden_protocol::account::AccountId;
5use miden_protocol::block::BlockHeader;
6use miden_protocol::crypto::merkle::MerklePath;
7use miden_protocol::note::{
8 Note,
9 NoteAttachmentHeader,
10 NoteAttachmentScheme,
11 NoteAttachments,
12 NoteDetails,
13 NoteDetailsCommitment,
14 NoteHeader,
15 NoteId,
16 NoteInclusionProof,
17 NoteMetadata,
18 NoteScript,
19 NoteTag,
20 NoteType,
21 PartialNoteMetadata,
22};
23use miden_protocol::{MastForest, MastNodeId, Word};
24use miden_tx::utils::serde::Deserializable;
25
26use super::{MissingFieldHelper, RpcConversionError};
27use crate::rpc::{RpcError, generated as proto};
28
29impl From<NoteId> for proto::note::NoteId {
30 fn from(value: NoteId) -> Self {
31 proto::note::NoteId { id: Some(value.into()) }
32 }
33}
34
35impl TryFrom<proto::note::NoteId> for NoteId {
36 type Error = RpcConversionError;
37
38 fn try_from(value: proto::note::NoteId) -> Result<Self, Self::Error> {
39 let word =
40 Word::try_from(value.id.ok_or(proto::note::NoteId::missing_field(stringify!(id)))?)?;
41 Ok(Self::from_raw(word))
42 }
43}
44
45fn note_type_from_proto(raw: i32) -> Result<NoteType, RpcConversionError> {
46 let proto_note_type = proto::note::NoteType::try_from(raw)
47 .map_err(|_| RpcConversionError::InvalidField(alloc::format!("note_type={raw}")))?;
48 match proto_note_type {
49 proto::note::NoteType::Public => Ok(NoteType::Public),
50 proto::note::NoteType::Private => Ok(NoteType::Private),
51 proto::note::NoteType::Unspecified => {
52 Err(RpcConversionError::InvalidField("note_type=NOTE_TYPE_UNSPECIFIED".into()))
53 },
54 }
55}
56
57fn note_type_to_proto(note_type: NoteType) -> i32 {
58 let proto_note_type = match note_type {
59 NoteType::Public => proto::note::NoteType::Public,
60 NoteType::Private => proto::note::NoteType::Private,
61 };
62 proto_note_type as i32
63}
64
65fn attachment_headers_from_proto(
69 schemes: &[u32],
70) -> Result<[NoteAttachmentHeader; NoteAttachments::MAX_COUNT], RpcConversionError> {
71 if schemes.len() > NoteAttachments::MAX_COUNT {
72 return Err(RpcConversionError::InvalidField(alloc::format!(
73 "attachment_schemes length {} exceeds NoteAttachments::MAX_COUNT",
74 schemes.len(),
75 )));
76 }
77 let mut headers = [NoteAttachmentHeader::absent(); NoteAttachments::MAX_COUNT];
78 for (slot, raw) in schemes.iter().enumerate() {
79 if *raw == 0 {
80 continue;
81 }
82 let raw_u16 = u16::try_from(*raw).map_err(|_| {
83 RpcConversionError::InvalidField(alloc::format!(
84 "attachment_schemes[{slot}]={raw} does not fit in u16",
85 ))
86 })?;
87 let scheme = NoteAttachmentScheme::new(raw_u16).map_err(|err| {
88 RpcConversionError::InvalidField(alloc::format!("attachment_schemes[{slot}]: {err}"))
89 })?;
90 headers[slot] = NoteAttachmentHeader::new(scheme);
91 }
92 Ok(headers)
93}
94
95fn attachment_schemes_to_proto(
96 headers: &[NoteAttachmentHeader; NoteAttachments::MAX_COUNT],
97) -> Vec<u32> {
98 let mut encoded: Vec<u32> = headers
101 .iter()
102 .map(|h| h.scheme().map_or(0, |s| u32::from(s.as_u16())))
103 .collect();
104 while matches!(encoded.last(), Some(0)) {
105 encoded.pop();
106 }
107 encoded
108}
109
110impl TryFrom<proto::note::NoteMetadata> for NoteMetadata {
111 type Error = RpcConversionError;
112
113 fn try_from(value: proto::note::NoteMetadata) -> Result<Self, Self::Error> {
114 let partial_metadata: PartialNoteMetadata = (&value).try_into()?;
115 let attachment_headers = attachment_headers_from_proto(&value.attachment_schemes)?;
116 let attachments_commitment = value
117 .attachments_commitment
118 .ok_or_else(|| {
119 proto::note::NoteMetadata::missing_field(stringify!(attachments_commitment))
120 })?
121 .try_into()?;
122
123 Ok(NoteMetadata::from_parts(
124 partial_metadata,
125 attachment_headers,
126 attachments_commitment,
127 ))
128 }
129}
130
131impl TryFrom<&proto::note::NoteMetadata> for PartialNoteMetadata {
132 type Error = RpcConversionError;
133
134 fn try_from(value: &proto::note::NoteMetadata) -> Result<Self, Self::Error> {
135 let sender = value
136 .sender
137 .clone()
138 .ok_or_else(|| proto::note::NoteMetadata::missing_field(stringify!(sender)))?
139 .try_into()?;
140 let note_type = note_type_from_proto(value.note_type)?;
141 let tag = NoteTag::new(value.tag);
142
143 Ok(PartialNoteMetadata::new(sender, note_type).with_tag(tag))
144 }
145}
146
147impl From<NoteMetadata> for proto::note::NoteMetadata {
148 fn from(value: NoteMetadata) -> Self {
149 proto::note::NoteMetadata {
150 sender: Some(value.sender().into()),
151 note_type: note_type_to_proto(value.note_type()),
152 tag: value.tag().as_u32(),
153 attachment_schemes: attachment_schemes_to_proto(value.attachment_headers()),
154 attachments_commitment: Some(value.attachments_commitment().into()),
155 }
156 }
157}
158
159impl TryFrom<proto::note::NoteHeader> for NoteHeader {
160 type Error = RpcConversionError;
161
162 fn try_from(value: proto::note::NoteHeader) -> Result<Self, Self::Error> {
163 let details_commitment_word: Word = value
164 .details_commitment
165 .ok_or(proto::note::NoteHeader::missing_field(stringify!(details_commitment)))?
166 .try_into()?;
167 let metadata = value
168 .metadata
169 .ok_or(proto::note::NoteHeader::missing_field(stringify!(metadata)))?
170 .try_into()?;
171 Ok(NoteHeader::new(
172 NoteDetailsCommitment::from_raw(details_commitment_word),
173 metadata,
174 ))
175 }
176}
177
178impl TryFrom<proto::note::NoteInclusionInBlockProof> for NoteInclusionProof {
179 type Error = RpcConversionError;
180
181 fn try_from(value: proto::note::NoteInclusionInBlockProof) -> Result<Self, Self::Error> {
182 Ok(NoteInclusionProof::new(
183 value.block_num.into(),
184 u16::try_from(value.note_index_in_block)
185 .map_err(|_| RpcConversionError::InvalidField("NoteIndexInBlock".into()))?,
186 value
187 .inclusion_path
188 .ok_or_else(|| {
189 proto::note::NoteInclusionInBlockProof::missing_field(stringify!(
190 inclusion_path
191 ))
192 })?
193 .try_into()?,
194 )?)
195 }
196}
197
198#[derive(Debug, Clone)]
203pub struct NoteSyncBlock {
204 pub block_header: BlockHeader,
206 pub mmr_path: MerklePath,
208 pub notes: BTreeMap<NoteId, CommittedNote>,
210}
211
212#[allow(clippy::large_enum_variant)]
216pub enum SyncedNoteDetails {
217 Public(Note),
219 Private(Option<NoteAttachments>),
222}
223
224impl TryFrom<proto::rpc::sync_notes_response::NoteSyncBlock> for NoteSyncBlock {
225 type Error = RpcError;
226
227 fn try_from(
228 block: proto::rpc::sync_notes_response::NoteSyncBlock,
229 ) -> Result<Self, Self::Error> {
230 let block_header = block
231 .block_header
232 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(blocks.block_header)))?
233 .try_into()?;
234
235 let mmr_path = block
236 .mmr_path
237 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(blocks.mmr_path)))?
238 .try_into()?;
239
240 let notes: BTreeMap<NoteId, CommittedNote> = block
241 .notes
242 .into_iter()
243 .map(|n| {
244 let note = CommittedNote::try_from(n)?;
245 Ok((*note.note_id(), note))
246 })
247 .collect::<Result<_, RpcConversionError>>()?;
248
249 Ok(NoteSyncBlock { block_header, mmr_path, notes })
250 }
251}
252
253#[derive(Debug, Clone)]
258pub struct CommittedNote {
259 note_id: NoteId,
261 metadata: NoteMetadata,
265 inclusion_proof: NoteInclusionProof,
267}
268
269impl CommittedNote {
270 pub fn new(
271 note_id: NoteId,
272 metadata: NoteMetadata,
273 inclusion_proof: NoteInclusionProof,
274 ) -> Self {
275 Self { note_id, metadata, inclusion_proof }
276 }
277
278 pub fn note_id(&self) -> &NoteId {
279 &self.note_id
280 }
281
282 pub fn note_type(&self) -> NoteType {
283 self.metadata.note_type()
284 }
285
286 pub fn tag(&self) -> NoteTag {
287 self.metadata.tag()
288 }
289
290 pub fn sender(&self) -> AccountId {
291 self.metadata.sender()
292 }
293
294 pub fn metadata(&self) -> &NoteMetadata {
296 &self.metadata
297 }
298
299 pub fn inclusion_proof(&self) -> &NoteInclusionProof {
300 &self.inclusion_proof
301 }
302}
303
304impl TryFrom<proto::note::NoteSyncRecord> for CommittedNote {
305 type Error = RpcConversionError;
306
307 fn try_from(note: proto::note::NoteSyncRecord) -> Result<Self, Self::Error> {
308 let proto_metadata = note
309 .metadata
310 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(notes.metadata)))?;
311 let metadata: NoteMetadata = proto_metadata.try_into()?;
312
313 let proto_inclusion_proof = note.inclusion_proof.ok_or(
314 proto::rpc::SyncNotesResponse::missing_field(stringify!(notes.inclusion_proof)),
315 )?;
316
317 let note_id: NoteId = proto_inclusion_proof
318 .note_id
319 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(
320 notes.inclusion_proof.note_id
321 )))?
322 .try_into()?;
323
324 let inclusion_proof: NoteInclusionProof = proto_inclusion_proof.try_into()?;
325
326 Ok(CommittedNote::new(note_id, metadata, inclusion_proof))
327 }
328}
329
330#[allow(clippy::large_enum_variant)]
335pub enum FetchedNote {
336 Private(NoteId, NoteMetadata, NoteAttachments, NoteInclusionProof),
342 Public(Note, NoteInclusionProof),
344}
345
346impl FetchedNote {
347 pub fn inclusion_proof(&self) -> &NoteInclusionProof {
349 match self {
350 FetchedNote::Private(_, _, _, inclusion_proof)
351 | FetchedNote::Public(_, inclusion_proof) => inclusion_proof,
352 }
353 }
354
355 pub fn metadata(&self) -> &NoteMetadata {
357 match self {
358 FetchedNote::Private(_, metadata, ..) => metadata,
359 FetchedNote::Public(note, _) => note.metadata(),
360 }
361 }
362
363 pub fn attachments(&self) -> &NoteAttachments {
365 match self {
366 FetchedNote::Private(_, _, attachments, _) => attachments,
367 FetchedNote::Public(note, _) => note.attachments(),
368 }
369 }
370
371 pub fn id(&self) -> NoteId {
373 match self {
374 FetchedNote::Private(note_id, ..) => *note_id,
375 FetchedNote::Public(note, _) => note.id(),
376 }
377 }
378}
379
380impl TryFrom<proto::note::CommittedNote> for FetchedNote {
381 type Error = RpcConversionError;
382
383 fn try_from(value: proto::note::CommittedNote) -> Result<Self, Self::Error> {
384 let inclusion_proof = value.inclusion_proof.ok_or_else(|| {
385 proto::note::CommittedNote::missing_field(stringify!(inclusion_proof))
386 })?;
387
388 let note_id: NoteId = inclusion_proof
389 .note_id
390 .ok_or_else(|| {
391 proto::note::CommittedNote::missing_field(stringify!(inclusion_proof.note_id))
392 })?
393 .try_into()?;
394
395 let inclusion_proof = NoteInclusionProof::try_from(inclusion_proof)?;
396
397 let note = value
398 .note
399 .ok_or_else(|| proto::note::CommittedNote::missing_field(stringify!(note)))?;
400
401 let proto_metadata = note
402 .metadata
403 .ok_or_else(|| proto::note::CommittedNote::missing_field(stringify!(note.metadata)))?;
404 let metadata: NoteMetadata = proto_metadata.clone().try_into()?;
405 let partial_metadata: PartialNoteMetadata = (&proto_metadata).try_into()?;
406
407 let attachments = if note.attachments.is_empty() {
408 NoteAttachments::empty()
409 } else {
410 NoteAttachments::read_from_bytes(¬e.attachments)?
411 };
412
413 if let Some(detail_bytes) = note.details {
414 let details = NoteDetails::read_from_bytes(&detail_bytes)?;
415 let (assets, recipient) = details.into_parts();
416
417 Ok(FetchedNote::Public(
418 Note::with_attachments(assets, partial_metadata, recipient, attachments),
419 inclusion_proof,
420 ))
421 } else {
422 Ok(FetchedNote::Private(note_id, metadata, attachments, inclusion_proof))
423 }
424 }
425}
426
427impl TryFrom<proto::note::NoteScript> for NoteScript {
431 type Error = RpcConversionError;
432
433 fn try_from(note_script: proto::note::NoteScript) -> Result<Self, Self::Error> {
434 let mast_forest = MastForest::read_from_bytes(¬e_script.mast)?;
435 let entrypoint = MastNodeId::from_u32_safe(note_script.entrypoint, &mast_forest)?;
436 Ok(NoteScript::from_parts(alloc::sync::Arc::new(mast_forest), entrypoint))
437 }
438}