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 = NoteType::try_from(u64::from(value.note_type))?;
48 let tag = NoteTag::new(value.tag);
49
50 let attachment = if value.attachment.is_empty() {
52 NoteAttachment::default()
53 } else {
54 NoteAttachment::read_from_bytes(&value.attachment)
55 .map_err(RpcConversionError::DeserializationError)?
56 };
57
58 Ok(NoteMetadata::new(sender, note_type, tag).with_attachment(attachment))
59 }
60}
61
62impl From<NoteMetadata> for proto::note::NoteMetadata {
63 fn from(value: NoteMetadata) -> Self {
64 use miden_tx::utils::Serializable;
65 proto::note::NoteMetadata {
66 sender: Some(value.sender().into()),
67 note_type: value.note_type() as u32,
68 tag: value.tag().as_u32(),
69 attachment: value.attachment().to_bytes(),
70 }
71 }
72}
73
74impl TryFrom<proto::note::NoteInclusionInBlockProof> for NoteInclusionProof {
75 type Error = RpcConversionError;
76
77 fn try_from(value: proto::note::NoteInclusionInBlockProof) -> Result<Self, Self::Error> {
78 Ok(NoteInclusionProof::new(
79 value.block_num.into(),
80 u16::try_from(value.note_index_in_block)
81 .map_err(|_| RpcConversionError::InvalidField("NoteIndexInBlock".into()))?,
82 value
83 .inclusion_path
84 .ok_or_else(|| {
85 proto::note::NoteInclusionInBlockProof::missing_field(stringify!(
86 inclusion_path
87 ))
88 })?
89 .try_into()?,
90 )?)
91 }
92}
93
94#[derive(Debug)]
99pub struct NoteSyncInfo {
100 pub chain_tip: BlockNumber,
102 pub block_header: BlockHeader,
104 pub mmr_path: MerklePath,
111 pub notes: Vec<CommittedNote>,
113}
114
115impl TryFrom<proto::rpc::SyncNotesResponse> for NoteSyncInfo {
116 type Error = RpcError;
117
118 fn try_from(value: proto::rpc::SyncNotesResponse) -> Result<Self, Self::Error> {
119 let chain_tip = value
120 .pagination_info
121 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(pagination_info)))?
122 .chain_tip;
123
124 let block_header = value
126 .block_header
127 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(block_header)))?
128 .try_into()?;
129
130 let mmr_path = value
131 .mmr_path
132 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(mmr_path)))?
133 .try_into()?;
134
135 let mut notes = vec![];
137 for note in value.notes {
138 let note_id: NoteId = note
139 .note_id
140 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(notes.note_id)))?
141 .try_into()?;
142
143 let inclusion_path = note
144 .inclusion_path
145 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(
146 notes.inclusion_path
147 )))?
148 .try_into()?;
149
150 let metadata = note
151 .metadata
152 .ok_or(proto::rpc::SyncNotesResponse::missing_field(stringify!(notes.metadata)))?
153 .try_into()?;
154
155 let committed_note = CommittedNote::new(
156 note_id,
157 u16::try_from(note.note_index_in_block).expect("note index out of range"),
158 inclusion_path,
159 metadata,
160 );
161
162 notes.push(committed_note);
163 }
164
165 Ok(NoteSyncInfo {
166 chain_tip: chain_tip.into(),
167 block_header,
168 mmr_path,
169 notes,
170 })
171 }
172}
173
174#[derive(Debug, Clone)]
179pub struct CommittedNote {
180 note_id: NoteId,
182 note_index: u16,
184 inclusion_path: SparseMerklePath,
186 metadata: NoteMetadata,
188}
189
190impl CommittedNote {
191 pub fn new(
192 note_id: NoteId,
193 note_index: u16,
194 inclusion_path: SparseMerklePath,
195 metadata: NoteMetadata,
196 ) -> Self {
197 Self {
198 note_id,
199 note_index,
200 inclusion_path,
201 metadata,
202 }
203 }
204
205 pub fn note_id(&self) -> &NoteId {
206 &self.note_id
207 }
208
209 pub fn note_index(&self) -> u16 {
210 self.note_index
211 }
212
213 pub fn inclusion_path(&self) -> &SparseMerklePath {
214 &self.inclusion_path
215 }
216
217 pub fn metadata(&self) -> NoteMetadata {
218 self.metadata.clone()
219 }
220}
221
222#[allow(clippy::large_enum_variant)]
227pub enum FetchedNote {
228 Private(NoteHeader, NoteInclusionProof),
231 Public(Note, NoteInclusionProof),
233}
234
235impl FetchedNote {
236 pub fn inclusion_proof(&self) -> &NoteInclusionProof {
238 match self {
239 FetchedNote::Private(_, inclusion_proof) | FetchedNote::Public(_, inclusion_proof) => {
240 inclusion_proof
241 },
242 }
243 }
244
245 pub fn metadata(&self) -> &NoteMetadata {
247 match self {
248 FetchedNote::Private(header, _) => header.metadata(),
249 FetchedNote::Public(note, _) => note.metadata(),
250 }
251 }
252
253 pub fn id(&self) -> NoteId {
255 match self {
256 FetchedNote::Private(header, _) => header.id(),
257 FetchedNote::Public(note, _) => note.id(),
258 }
259 }
260}
261
262impl TryFrom<proto::note::CommittedNote> for FetchedNote {
263 type Error = RpcConversionError;
264
265 fn try_from(value: proto::note::CommittedNote) -> Result<Self, Self::Error> {
266 let inclusion_proof = value.inclusion_proof.ok_or_else(|| {
267 proto::note::CommittedNote::missing_field(stringify!(inclusion_proof))
268 })?;
269
270 let note_id: NoteId = inclusion_proof
271 .note_id
272 .ok_or_else(|| {
273 proto::note::CommittedNote::missing_field(stringify!(inclusion_proof.note_id))
274 })?
275 .try_into()?;
276
277 let inclusion_proof = NoteInclusionProof::try_from(inclusion_proof)?;
278
279 let note = value
280 .note
281 .ok_or_else(|| proto::note::CommittedNote::missing_field(stringify!(note)))?;
282
283 let metadata = note
284 .metadata
285 .ok_or_else(|| proto::note::CommittedNote::missing_field(stringify!(note.metadata)))?
286 .try_into()?;
287
288 if let Some(detail_bytes) = note.details {
289 let details = NoteDetails::read_from_bytes(&detail_bytes)?;
290 let (assets, recipient) = details.into_parts();
291
292 Ok(FetchedNote::Public(Note::new(assets, metadata, recipient), inclusion_proof))
293 } else {
294 let note_header = NoteHeader::new(note_id, metadata);
295 Ok(FetchedNote::Private(note_header, inclusion_proof))
296 }
297 }
298}
299
300impl TryFrom<proto::note::NoteScript> for NoteScript {
304 type Error = RpcConversionError;
305
306 fn try_from(note_script: proto::note::NoteScript) -> Result<Self, Self::Error> {
307 let mast_forest = MastForest::read_from_bytes(¬e_script.mast)?;
308 let entrypoint = MastNodeId::from_u32_safe(note_script.entrypoint, &mast_forest)?;
309 Ok(NoteScript::from_parts(alloc::sync::Arc::new(mast_forest), entrypoint))
310 }
311}