miden_client/rpc/domain/
note.rs1use alloc::vec::Vec;
2
3use miden_protocol::block::{BlockHeader, BlockNumber};
4use miden_protocol::crypto::merkle::{MerklePath, SparseMerklePath};
5use miden_protocol::note::{
6 Note,
7 NoteAttachment,
8 NoteDetails,
9 NoteHeader,
10 NoteId,
11 NoteInclusionProof,
12 NoteMetadata,
13 NoteScript,
14 NoteTag,
15 NoteType,
16};
17use miden_protocol::{MastForest, MastNodeId, Word};
18use miden_tx::utils::Deserializable;
19
20use super::{MissingFieldHelper, RpcConversionError};
21use crate::rpc::{RpcError, generated as proto};
22
23impl From<NoteId> for proto::note::NoteId {
24 fn from(value: NoteId) -> Self {
25 proto::note::NoteId { id: Some(value.into()) }
26 }
27}
28
29impl TryFrom<proto::note::NoteId> for NoteId {
30 type Error = RpcConversionError;
31
32 fn try_from(value: proto::note::NoteId) -> Result<Self, Self::Error> {
33 let word =
34 Word::try_from(value.id.ok_or(proto::note::NoteId::missing_field(stringify!(id)))?)?;
35 Ok(Self::from_raw(word))
36 }
37}
38
39impl TryFrom<proto::note::NoteMetadata> for NoteMetadata {
40 type Error = RpcConversionError;
41
42 fn try_from(value: proto::note::NoteMetadata) -> Result<Self, Self::Error> {
43 let sender = value
44 .sender
45 .ok_or_else(|| proto::note::NoteMetadata::missing_field(stringify!(sender)))?
46 .try_into()?;
47 let note_type =
48 NoteType::try_from(u64::try_from(value.note_type).expect("invalid note type"))?;
49 let tag = NoteTag::new(value.tag);
50
51 let attachment = if value.attachment.is_empty() {
53 NoteAttachment::default()
54 } else {
55 NoteAttachment::read_from_bytes(&value.attachment)
56 .map_err(RpcConversionError::DeserializationError)?
57 };
58
59 Ok(NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment))
60 }
61}
62
63impl From<NoteMetadata> for proto::note::NoteMetadata {
64 fn from(value: NoteMetadata) -> Self {
65 use miden_tx::utils::Serializable;
66 proto::note::NoteMetadata {
67 sender: Some(value.sender().into()),
68 note_type: value.note_type() as i32,
69 tag: value.tag().as_u32(),
70 attachment: value.attachment().to_bytes(),
71 }
72 }
73}
74
75impl TryFrom<proto::note::NoteInclusionInBlockProof> for NoteInclusionProof {
76 type Error = RpcConversionError;
77
78 fn try_from(value: proto::note::NoteInclusionInBlockProof) -> Result<Self, Self::Error> {
79 Ok(NoteInclusionProof::new(
80 value.block_num.into(),
81 u16::try_from(value.note_index_in_block)
82 .map_err(|_| RpcConversionError::InvalidField("NoteIndexInBlock".into()))?,
83 value
84 .inclusion_path
85 .ok_or_else(|| {
86 proto::note::NoteInclusionInBlockProof::missing_field(stringify!(
87 inclusion_path
88 ))
89 })?
90 .try_into()?,
91 )?)
92 }
93}
94
95#[derive(Debug)]
100pub struct NoteSyncInfo {
101 pub chain_tip: BlockNumber,
103 pub block_header: BlockHeader,
105 pub mmr_path: MerklePath,
112 pub notes: Vec<CommittedNote>,
114}
115
116impl TryFrom<proto::rpc::SyncNotesResponse> for NoteSyncInfo {
117 type Error = RpcError;
118
119 fn try_from(value: proto::rpc::SyncNotesResponse) -> Result<Self, Self::Error> {
120 let chain_tip = value
121 .pagination_info
122 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(pagination_info)))?
123 .chain_tip;
124
125 let block_header = value
127 .block_header
128 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(block_header)))?
129 .try_into()?;
130
131 let mmr_path = value
132 .mmr_path
133 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(mmr_path)))?
134 .try_into()?;
135
136 let mut notes = vec![];
138 for note in value.notes {
139 let note_id: NoteId = note
140 .note_id
141 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(notes.note_id)))?
142 .try_into()?;
143
144 let inclusion_path = note
145 .inclusion_path
146 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(
147 notes.inclusion_path
148 )))?
149 .try_into()?;
150
151 let metadata = note
152 .metadata
153 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(notes.metadata)))?
154 .try_into()?;
155
156 let committed_note = CommittedNote::new(
157 note_id,
158 u16::try_from(note.note_index_in_block).map_err(|_| {
159 RpcConversionError::InvalidField(
160 "note_index_in_block value out of u16 range".into(),
161 )
162 })?,
163 inclusion_path,
164 metadata,
165 );
166
167 notes.push(committed_note);
168 }
169
170 Ok(NoteSyncInfo {
171 chain_tip: chain_tip.into(),
172 block_header,
173 mmr_path,
174 notes,
175 })
176 }
177}
178
179#[derive(Debug, Clone)]
184pub struct CommittedNote {
185 note_id: NoteId,
187 note_index: u16,
189 inclusion_path: SparseMerklePath,
191 metadata: NoteMetadata,
193}
194
195impl CommittedNote {
196 pub fn new(
197 note_id: NoteId,
198 note_index: u16,
199 inclusion_path: SparseMerklePath,
200 metadata: NoteMetadata,
201 ) -> Self {
202 Self {
203 note_id,
204 note_index,
205 inclusion_path,
206 metadata,
207 }
208 }
209
210 pub fn note_id(&self) -> &NoteId {
211 &self.note_id
212 }
213
214 pub fn note_index(&self) -> u16 {
215 self.note_index
216 }
217
218 pub fn inclusion_path(&self) -> &SparseMerklePath {
219 &self.inclusion_path
220 }
221
222 pub fn metadata(&self) -> NoteMetadata {
223 self.metadata.clone()
224 }
225}
226
227#[allow(clippy::large_enum_variant)]
232pub enum FetchedNote {
233 Private(NoteHeader, NoteInclusionProof),
236 Public(Note, NoteInclusionProof),
238}
239
240impl FetchedNote {
241 pub fn inclusion_proof(&self) -> &NoteInclusionProof {
243 match self {
244 FetchedNote::Private(_, inclusion_proof) | FetchedNote::Public(_, inclusion_proof) => {
245 inclusion_proof
246 },
247 }
248 }
249
250 pub fn metadata(&self) -> &NoteMetadata {
252 match self {
253 FetchedNote::Private(header, _) => header.metadata(),
254 FetchedNote::Public(note, _) => note.metadata(),
255 }
256 }
257
258 pub fn id(&self) -> NoteId {
260 match self {
261 FetchedNote::Private(header, _) => header.id(),
262 FetchedNote::Public(note, _) => note.id(),
263 }
264 }
265}
266
267impl TryFrom<proto::note::CommittedNote> for FetchedNote {
268 type Error = RpcConversionError;
269
270 fn try_from(value: proto::note::CommittedNote) -> Result<Self, Self::Error> {
271 let inclusion_proof = value.inclusion_proof.ok_or_else(|| {
272 proto::note::CommittedNote::missing_field(stringify!(inclusion_proof))
273 })?;
274
275 let note_id: NoteId = inclusion_proof
276 .note_id
277 .ok_or_else(|| {
278 proto::note::CommittedNote::missing_field(stringify!(inclusion_proof.note_id))
279 })?
280 .try_into()?;
281
282 let inclusion_proof = NoteInclusionProof::try_from(inclusion_proof)?;
283
284 let note = value
285 .note
286 .ok_or_else(|| proto::note::CommittedNote::missing_field(stringify!(note)))?;
287
288 let metadata = note
289 .metadata
290 .ok_or_else(|| proto::note::CommittedNote::missing_field(stringify!(note.metadata)))?
291 .try_into()?;
292
293 if let Some(detail_bytes) = note.details {
294 let details = NoteDetails::read_from_bytes(&detail_bytes)?;
295 let (assets, recipient) = details.into_parts();
296
297 Ok(FetchedNote::Public(Note::new(assets, metadata, recipient), inclusion_proof))
298 } else {
299 let note_header = NoteHeader::new(note_id, metadata);
300 Ok(FetchedNote::Private(note_header, inclusion_proof))
301 }
302 }
303}
304
305impl TryFrom<proto::note::NoteScript> for NoteScript {
309 type Error = RpcConversionError;
310
311 fn try_from(note_script: proto::note::NoteScript) -> Result<Self, Self::Error> {
312 let mast_forest = MastForest::read_from_bytes(¬e_script.mast)?;
313 let entrypoint = MastNodeId::from_u32_safe(note_script.entrypoint, &mast_forest)?;
314 Ok(NoteScript::from_parts(alloc::sync::Arc::new(mast_forest), entrypoint))
315 }
316}