Skip to main content

miden_node_proto/domain/
note.rs

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