1use alloc::collections::BTreeMap;
2use alloc::vec::Vec;
3
4use miden_protocol::account::AccountId;
5use miden_protocol::block::{BlockHeader, BlockNumber};
6use miden_protocol::crypto::merkle::MerklePath;
7use miden_protocol::note::{
8 Note,
9 NoteAttachment,
10 NoteAttachmentKind,
11 NoteDetails,
12 NoteHeader,
13 NoteId,
14 NoteInclusionProof,
15 NoteMetadata,
16 NoteScript,
17 NoteTag,
18 NoteType,
19};
20use miden_protocol::{MastForest, MastNodeId, Word};
21use miden_tx::utils::serde::Deserializable;
22
23use super::{MissingFieldHelper, RpcConversionError};
24use crate::rpc::{RpcError, generated as proto};
25
26impl From<NoteId> for proto::note::NoteId {
27 fn from(value: NoteId) -> Self {
28 proto::note::NoteId { id: Some(value.into()) }
29 }
30}
31
32impl TryFrom<proto::note::NoteId> for NoteId {
33 type Error = RpcConversionError;
34
35 fn try_from(value: proto::note::NoteId) -> Result<Self, Self::Error> {
36 let word =
37 Word::try_from(value.id.ok_or(proto::note::NoteId::missing_field(stringify!(id)))?)?;
38 Ok(Self::from_raw(word))
39 }
40}
41
42impl TryFrom<proto::note::NoteMetadata> for NoteMetadata {
43 type Error = RpcConversionError;
44
45 fn try_from(value: proto::note::NoteMetadata) -> Result<Self, Self::Error> {
46 let sender = value
47 .sender
48 .ok_or_else(|| proto::note::NoteMetadata::missing_field(stringify!(sender)))?
49 .try_into()?;
50 let note_type =
51 NoteType::try_from(u64::try_from(value.note_type).expect("invalid note type"))?;
52 let tag = NoteTag::new(value.tag);
53
54 let attachment = if value.attachment.is_empty() {
56 NoteAttachment::default()
57 } else {
58 NoteAttachment::read_from_bytes(&value.attachment)
59 .map_err(RpcConversionError::DeserializationError)?
60 };
61
62 Ok(NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment))
63 }
64}
65
66impl From<NoteMetadata> for proto::note::NoteMetadata {
67 fn from(value: NoteMetadata) -> Self {
68 use miden_tx::utils::serde::Serializable;
69 proto::note::NoteMetadata {
70 sender: Some(value.sender().into()),
71 note_type: value.note_type() as i32,
72 tag: value.tag().as_u32(),
73 attachment: value.attachment().to_bytes(),
74 }
75 }
76}
77
78impl TryFrom<proto::note::NoteHeader> for NoteHeader {
79 type Error = RpcConversionError;
80
81 fn try_from(value: proto::note::NoteHeader) -> Result<Self, Self::Error> {
82 let note_id = value
83 .note_id
84 .ok_or(proto::note::NoteHeader::missing_field(stringify!(note_id)))?
85 .try_into()?;
86 let metadata = value
87 .metadata
88 .ok_or(proto::note::NoteHeader::missing_field(stringify!(metadata)))?
89 .try_into()?;
90 Ok(NoteHeader::new(note_id, metadata))
91 }
92}
93
94impl TryFrom<proto::note::NoteInclusionInBlockProof> for NoteInclusionProof {
95 type Error = RpcConversionError;
96
97 fn try_from(value: proto::note::NoteInclusionInBlockProof) -> Result<Self, Self::Error> {
98 Ok(NoteInclusionProof::new(
99 value.block_num.into(),
100 u16::try_from(value.note_index_in_block)
101 .map_err(|_| RpcConversionError::InvalidField("NoteIndexInBlock".into()))?,
102 value
103 .inclusion_path
104 .ok_or_else(|| {
105 proto::note::NoteInclusionInBlockProof::missing_field(stringify!(
106 inclusion_path
107 ))
108 })?
109 .try_into()?,
110 )?)
111 }
112}
113
114#[derive(Debug, Clone)]
119pub struct NoteSyncBlock {
120 pub block_header: BlockHeader,
122 pub mmr_path: MerklePath,
124 pub notes: BTreeMap<NoteId, CommittedNote>,
126}
127
128#[derive(Debug)]
133pub struct NoteSyncInfo {
134 pub chain_tip: BlockNumber,
136 pub block_to: BlockNumber,
139 pub blocks: Vec<NoteSyncBlock>,
142}
143
144pub struct SyncNotesResult {
151 pub blocks: Vec<NoteSyncBlock>,
155 pub public_notes: BTreeMap<NoteId, Note>,
157}
158
159impl TryFrom<proto::rpc::SyncNotesResponse> for NoteSyncInfo {
160 type Error = RpcError;
161
162 fn try_from(value: proto::rpc::SyncNotesResponse) -> Result<Self, Self::Error> {
163 let pagination_info = value
164 .pagination_info
165 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(pagination_info)))?;
166
167 let chain_tip = BlockNumber::from(pagination_info.chain_tip);
168 let block_to = BlockNumber::from(pagination_info.block_num);
169
170 let blocks = value
171 .blocks
172 .into_iter()
173 .map(|block| {
174 let block_header = block
175 .block_header
176 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(
177 blocks.block_header
178 )))?
179 .try_into()?;
180
181 let mmr_path = block
182 .mmr_path
183 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(
184 blocks.mmr_path
185 )))?
186 .try_into()?;
187
188 let notes: BTreeMap<NoteId, CommittedNote> = block
189 .notes
190 .into_iter()
191 .map(|n| {
192 let note = CommittedNote::try_from(n)?;
193 Ok((*note.note_id(), note))
194 })
195 .collect::<Result<_, RpcConversionError>>()?;
196
197 Ok(NoteSyncBlock { block_header, mmr_path, notes })
198 })
199 .collect::<Result<Vec<_>, RpcError>>()?;
200
201 Ok(NoteSyncInfo { chain_tip, block_to, blocks })
202 }
203}
204
205#[derive(Debug, Clone)]
215pub enum CommittedNoteMetadata {
216 Full(NoteMetadata),
218 Header {
222 sender: AccountId,
223 note_type: NoteType,
224 tag: NoteTag,
225 attachment_kind: NoteAttachmentKind,
226 },
227}
228
229impl CommittedNoteMetadata {
230 pub fn metadata(&self) -> Option<&NoteMetadata> {
232 match self {
233 Self::Full(m) => Some(m),
234 Self::Header { .. } => None,
235 }
236 }
237}
238
239#[derive(Debug, Clone)]
246pub struct CommittedNote {
247 note_id: NoteId,
249 metadata: CommittedNoteMetadata,
252 inclusion_proof: NoteInclusionProof,
254}
255
256impl CommittedNote {
257 pub fn new(
258 note_id: NoteId,
259 metadata: CommittedNoteMetadata,
260 inclusion_proof: NoteInclusionProof,
261 ) -> Self {
262 Self { note_id, metadata, inclusion_proof }
263 }
264
265 pub fn note_id(&self) -> &NoteId {
266 &self.note_id
267 }
268
269 pub fn note_type(&self) -> NoteType {
270 match &self.metadata {
271 CommittedNoteMetadata::Full(m) => m.note_type(),
272 CommittedNoteMetadata::Header { note_type, .. } => *note_type,
273 }
274 }
275
276 pub fn tag(&self) -> NoteTag {
277 match &self.metadata {
278 CommittedNoteMetadata::Full(m) => m.tag(),
279 CommittedNoteMetadata::Header { tag, .. } => *tag,
280 }
281 }
282
283 pub fn sender(&self) -> AccountId {
284 match &self.metadata {
285 CommittedNoteMetadata::Full(m) => m.sender(),
286 CommittedNoteMetadata::Header { sender, .. } => *sender,
287 }
288 }
289
290 pub fn metadata(&self) -> Option<&NoteMetadata> {
292 self.metadata.metadata()
293 }
294
295 pub fn committed_metadata(&self) -> &CommittedNoteMetadata {
297 &self.metadata
298 }
299
300 pub fn set_metadata(&mut self, metadata: NoteMetadata) {
305 self.metadata = CommittedNoteMetadata::Full(metadata);
306 }
307
308 pub fn inclusion_proof(&self) -> &NoteInclusionProof {
309 &self.inclusion_proof
310 }
311}
312
313impl TryFrom<proto::note::NoteSyncRecord> for CommittedNote {
314 type Error = RpcConversionError;
315
316 fn try_from(note: proto::note::NoteSyncRecord) -> Result<Self, Self::Error> {
317 let proto_header = note.metadata_header.ok_or(
318 proto::rpc::SyncNotesResponse::missing_field(stringify!(notes.metadata_header)),
319 )?;
320
321 let sender = proto_header
322 .sender
323 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(
324 notes.metadata_header.sender
325 )))?
326 .try_into()?;
327 let note_type =
328 NoteType::try_from(u64::try_from(proto_header.note_type).expect("invalid note type"))?;
329 let tag = NoteTag::new(proto_header.tag);
330 let attachment_kind = u8::try_from(proto_header.attachment_kind)
331 .ok()
332 .and_then(|kind| NoteAttachmentKind::try_from(kind).ok())
333 .unwrap_or_default();
334
335 let metadata = if attachment_kind == NoteAttachmentKind::None {
336 CommittedNoteMetadata::Full(NoteMetadata::new(sender, note_type).with_tag(tag))
337 } else {
338 CommittedNoteMetadata::Header { sender, note_type, tag, attachment_kind }
339 };
340
341 let proto_inclusion_proof = note.inclusion_proof.ok_or(
342 proto::rpc::SyncNotesResponse::missing_field(stringify!(notes.inclusion_proof)),
343 )?;
344
345 let note_id: NoteId = proto_inclusion_proof
346 .note_id
347 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(
348 notes.inclusion_proof.note_id
349 )))?
350 .try_into()?;
351
352 let inclusion_proof: NoteInclusionProof = proto_inclusion_proof.try_into()?;
353
354 Ok(CommittedNote::new(note_id, metadata, inclusion_proof))
355 }
356}
357
358#[allow(clippy::large_enum_variant)]
363pub enum FetchedNote {
364 Private(NoteHeader, NoteInclusionProof),
367 Public(Note, NoteInclusionProof),
369}
370
371impl FetchedNote {
372 pub fn inclusion_proof(&self) -> &NoteInclusionProof {
374 match self {
375 FetchedNote::Private(_, inclusion_proof) | FetchedNote::Public(_, inclusion_proof) => {
376 inclusion_proof
377 },
378 }
379 }
380
381 pub fn metadata(&self) -> &NoteMetadata {
383 match self {
384 FetchedNote::Private(header, _) => header.metadata(),
385 FetchedNote::Public(note, _) => note.metadata(),
386 }
387 }
388
389 pub fn id(&self) -> NoteId {
391 match self {
392 FetchedNote::Private(header, _) => header.id(),
393 FetchedNote::Public(note, _) => note.id(),
394 }
395 }
396}
397
398impl TryFrom<proto::note::CommittedNote> for FetchedNote {
399 type Error = RpcConversionError;
400
401 fn try_from(value: proto::note::CommittedNote) -> Result<Self, Self::Error> {
402 let inclusion_proof = value.inclusion_proof.ok_or_else(|| {
403 proto::note::CommittedNote::missing_field(stringify!(inclusion_proof))
404 })?;
405
406 let note_id: NoteId = inclusion_proof
407 .note_id
408 .ok_or_else(|| {
409 proto::note::CommittedNote::missing_field(stringify!(inclusion_proof.note_id))
410 })?
411 .try_into()?;
412
413 let inclusion_proof = NoteInclusionProof::try_from(inclusion_proof)?;
414
415 let note = value
416 .note
417 .ok_or_else(|| proto::note::CommittedNote::missing_field(stringify!(note)))?;
418
419 let metadata = note
420 .metadata
421 .ok_or_else(|| proto::note::CommittedNote::missing_field(stringify!(note.metadata)))?
422 .try_into()?;
423
424 if let Some(detail_bytes) = note.details {
425 let details = NoteDetails::read_from_bytes(&detail_bytes)?;
426 let (assets, recipient) = details.into_parts();
427
428 Ok(FetchedNote::Public(Note::new(assets, metadata, recipient), inclusion_proof))
429 } else {
430 let note_header = NoteHeader::new(note_id, metadata);
431 Ok(FetchedNote::Private(note_header, inclusion_proof))
432 }
433 }
434}
435
436impl TryFrom<proto::note::NoteScript> for NoteScript {
440 type Error = RpcConversionError;
441
442 fn try_from(note_script: proto::note::NoteScript) -> Result<Self, Self::Error> {
443 let mast_forest = MastForest::read_from_bytes(¬e_script.mast)?;
444 let entrypoint = MastNodeId::from_u32_safe(note_script.entrypoint, &mast_forest)?;
445 Ok(NoteScript::from_parts(alloc::sync::Arc::new(mast_forest), entrypoint))
446 }
447}