miden_node_proto/domain/
note.rs

1use miden_protocol::Word;
2use miden_protocol::block::BlockNumber;
3use miden_protocol::crypto::merkle::SparseMerklePath;
4use miden_protocol::note::{
5    Note,
6    NoteAttachment,
7    NoteDetails,
8    NoteId,
9    NoteInclusionProof,
10    NoteMetadata,
11    NoteScript,
12    NoteTag,
13    NoteType,
14    Nullifier,
15};
16use miden_protocol::utils::{Deserializable, Serializable};
17use miden_standards::note::{NetworkAccountTarget, NetworkAccountTargetError};
18use thiserror::Error;
19
20use super::account::NetworkAccountId;
21use crate::errors::{ConversionError, MissingFieldHelper};
22use crate::generated as proto;
23
24impl TryFrom<proto::note::NoteMetadata> for NoteMetadata {
25    type Error = ConversionError;
26
27    fn try_from(value: proto::note::NoteMetadata) -> Result<Self, Self::Error> {
28        let sender = value
29            .sender
30            .ok_or_else(|| proto::note::NoteMetadata::missing_field(stringify!(sender)))?
31            .try_into()?;
32        let note_type = NoteType::try_from(u64::from(value.note_type))?;
33        let tag = NoteTag::new(value.tag);
34
35        // Deserialize attachment if present
36        let attachment = if value.attachment.is_empty() {
37            NoteAttachment::default()
38        } else {
39            NoteAttachment::read_from_bytes(&value.attachment)
40                .map_err(|err| ConversionError::deserialization_error("NoteAttachment", err))?
41        };
42
43        Ok(NoteMetadata::new(sender, note_type, tag).with_attachment(attachment))
44    }
45}
46
47impl From<Note> for proto::note::NetworkNote {
48    fn from(note: Note) -> Self {
49        Self {
50            metadata: Some(proto::note::NoteMetadata::from(note.metadata().clone())),
51            details: NoteDetails::from(note).to_bytes(),
52        }
53    }
54}
55
56impl From<Note> for proto::note::Note {
57    fn from(note: Note) -> Self {
58        Self {
59            metadata: Some(proto::note::NoteMetadata::from(note.metadata().clone())),
60            details: Some(NoteDetails::from(note).to_bytes()),
61        }
62    }
63}
64
65impl From<NetworkNote> for proto::note::NetworkNote {
66    fn from(note: NetworkNote) -> Self {
67        let note = Note::from(note);
68        Self {
69            metadata: Some(proto::note::NoteMetadata::from(note.metadata().clone())),
70            details: NoteDetails::from(note).to_bytes(),
71        }
72    }
73}
74
75impl From<NoteMetadata> for proto::note::NoteMetadata {
76    fn from(val: NoteMetadata) -> Self {
77        let sender = Some(val.sender().into());
78        let note_type = val.note_type() as u32;
79        let tag = val.tag().as_u32();
80        let attachment = val.attachment().to_bytes();
81
82        proto::note::NoteMetadata { sender, note_type, tag, attachment }
83    }
84}
85
86impl From<Word> for proto::note::NoteId {
87    fn from(digest: Word) -> Self {
88        Self { id: Some(digest.into()) }
89    }
90}
91
92impl TryFrom<proto::note::NoteId> for Word {
93    type Error = ConversionError;
94
95    fn try_from(note_id: proto::note::NoteId) -> Result<Self, Self::Error> {
96        note_id
97            .id
98            .as_ref()
99            .ok_or(proto::note::NoteId::missing_field(stringify!(id)))?
100            .try_into()
101    }
102}
103
104impl From<&NoteId> for proto::note::NoteId {
105    fn from(note_id: &NoteId) -> Self {
106        Self { id: Some(note_id.into()) }
107    }
108}
109
110impl From<(&NoteId, &NoteInclusionProof)> for proto::note::NoteInclusionInBlockProof {
111    fn from((note_id, proof): (&NoteId, &NoteInclusionProof)) -> Self {
112        Self {
113            note_id: Some(note_id.into()),
114            block_num: proof.location().block_num().as_u32(),
115            note_index_in_block: proof.location().node_index_in_block().into(),
116            inclusion_path: Some(proof.note_path().clone().into()),
117        }
118    }
119}
120
121impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusionProof) {
122    type Error = ConversionError;
123
124    fn try_from(
125        proof: &proto::note::NoteInclusionInBlockProof,
126    ) -> Result<(NoteId, NoteInclusionProof), Self::Error> {
127        let inclusion_path = SparseMerklePath::try_from(
128            proof
129                .inclusion_path
130                .as_ref()
131                .ok_or(proto::note::NoteInclusionInBlockProof::missing_field(stringify!(
132                    inclusion_path
133                )))?
134                .clone(),
135        )?;
136
137        let note_id = Word::try_from(
138            proof
139                .note_id
140                .as_ref()
141                .ok_or(proto::note::NoteInclusionInBlockProof::missing_field(stringify!(note_id)))?
142                .id
143                .as_ref()
144                .ok_or(proto::note::NoteId::missing_field(stringify!(id)))?,
145        )?;
146
147        Ok((
148            NoteId::from_raw(note_id),
149            NoteInclusionProof::new(
150                proof.block_num.into(),
151                proof.note_index_in_block.try_into()?,
152                inclusion_path,
153            )?,
154        ))
155    }
156}
157
158impl TryFrom<proto::note::Note> for Note {
159    type Error = ConversionError;
160
161    fn try_from(proto_note: proto::note::Note) -> Result<Self, Self::Error> {
162        let metadata: NoteMetadata = proto_note
163            .metadata
164            .ok_or(proto::note::Note::missing_field(stringify!(metadata)))?
165            .try_into()?;
166
167        let details = proto_note
168            .details
169            .ok_or(proto::note::Note::missing_field(stringify!(details)))?;
170
171        let note_details = NoteDetails::read_from_bytes(&details)
172            .map_err(|err| ConversionError::deserialization_error("NoteDetails", err))?;
173
174        let (assets, recipient) = note_details.into_parts();
175        Ok(Note::new(assets, metadata, recipient))
176    }
177}
178
179// NETWORK NOTE
180// ================================================================================================
181
182/// An enum that wraps around notes used in a network mode.
183#[derive(Clone, Debug, PartialEq, Eq)]
184pub enum NetworkNote {
185    SingleTarget(SingleTargetNetworkNote),
186}
187
188impl NetworkNote {
189    pub fn inner(&self) -> &Note {
190        match self {
191            NetworkNote::SingleTarget(note) => note.inner(),
192        }
193    }
194
195    pub fn metadata(&self) -> &NoteMetadata {
196        self.inner().metadata()
197    }
198
199    pub fn nullifier(&self) -> Nullifier {
200        self.inner().nullifier()
201    }
202
203    pub fn id(&self) -> NoteId {
204        self.inner().id()
205    }
206}
207
208impl From<NetworkNote> for Note {
209    fn from(value: NetworkNote) -> Self {
210        match value {
211            NetworkNote::SingleTarget(note) => note.into(),
212        }
213    }
214}
215
216impl TryFrom<Note> for NetworkNote {
217    type Error = NetworkNoteError;
218
219    fn try_from(note: Note) -> Result<Self, Self::Error> {
220        SingleTargetNetworkNote::try_from(note).map(NetworkNote::SingleTarget)
221    }
222}
223
224impl TryFrom<proto::note::NetworkNote> for NetworkNote {
225    type Error = ConversionError;
226
227    fn try_from(proto_note: proto::note::NetworkNote) -> Result<Self, Self::Error> {
228        from_proto(proto_note)
229    }
230}
231
232// SINGLE TARGET NETWORK NOTE
233// ================================================================================================
234
235/// A newtype that wraps around notes targeting a single network account.
236///
237/// A note is considered a single-target network note if its attachment
238/// is a valid `NetworkAccountTarget`.
239#[derive(Clone, Debug, PartialEq, Eq)]
240pub struct SingleTargetNetworkNote {
241    note: Note,
242    account_target: NetworkAccountTarget,
243}
244
245impl SingleTargetNetworkNote {
246    pub fn inner(&self) -> &Note {
247        &self.note
248    }
249
250    pub fn metadata(&self) -> &NoteMetadata {
251        self.inner().metadata()
252    }
253
254    pub fn nullifier(&self) -> Nullifier {
255        self.inner().nullifier()
256    }
257
258    pub fn id(&self) -> NoteId {
259        self.inner().id()
260    }
261
262    /// The network account ID that this note targets.
263    pub fn account_id(&self) -> NetworkAccountId {
264        self.account_target.target_id().try_into().expect("always a network account ID")
265    }
266
267    pub fn can_be_consumed(&self, block_num: BlockNumber) -> Option<bool> {
268        self.account_target.execution_hint().can_be_consumed(block_num)
269    }
270}
271
272impl From<SingleTargetNetworkNote> for Note {
273    fn from(value: SingleTargetNetworkNote) -> Self {
274        value.note
275    }
276}
277
278impl TryFrom<Note> for SingleTargetNetworkNote {
279    type Error = NetworkNoteError;
280
281    fn try_from(note: Note) -> Result<Self, Self::Error> {
282        // Single-target network notes are identified by having a NetworkAccountTarget attachment
283        let attachment = note.metadata().attachment();
284        let account_target = NetworkAccountTarget::try_from(attachment)
285            .map_err(NetworkNoteError::InvalidAttachment)?;
286        Ok(Self { note, account_target })
287    }
288}
289
290impl TryFrom<proto::note::NetworkNote> for SingleTargetNetworkNote {
291    type Error = ConversionError;
292
293    fn try_from(proto_note: proto::note::NetworkNote) -> Result<Self, Self::Error> {
294        from_proto(proto_note)
295    }
296}
297
298/// Helper function to deduplicate implementations `TryFrom<proto::note::NetworkNote>`.
299fn from_proto<T>(proto_note: proto::note::NetworkNote) -> Result<T, ConversionError>
300where
301    T: TryFrom<Note>,
302    T::Error: Into<ConversionError>,
303{
304    let details = NoteDetails::read_from_bytes(&proto_note.details)
305        .map_err(|err| ConversionError::deserialization_error("NoteDetails", err))?;
306    let (assets, recipient) = details.into_parts();
307    let metadata: NoteMetadata = proto_note
308        .metadata
309        .ok_or_else(|| proto::note::NetworkNote::missing_field(stringify!(metadata)))?
310        .try_into()?;
311    let note = Note::new(assets, metadata, recipient);
312    T::try_from(note).map_err(Into::into)
313}
314
315#[derive(Debug, Error)]
316pub enum NetworkNoteError {
317    #[error("note does not have a valid NetworkAccountTarget attachment: {0}")]
318    InvalidAttachment(#[source] NetworkAccountTargetError),
319}
320
321// NOTE SCRIPT
322// ================================================================================================
323
324impl From<NoteScript> for proto::note::NoteScript {
325    fn from(script: NoteScript) -> Self {
326        Self {
327            entrypoint: script.entrypoint().into(),
328            mast: script.mast().to_bytes(),
329        }
330    }
331}