Skip to main content

miden_protocol/note/
file.rs

1#[cfg(feature = "std")]
2use std::{
3    fs::{self, File},
4    io::{self, Read},
5    path::Path,
6    vec::Vec,
7};
8
9use super::{Note, NoteDetails, NoteId, NoteInclusionProof, NoteTag};
10use crate::block::BlockNumber;
11#[cfg(feature = "std")]
12use crate::utils::serde::SliceReader;
13use crate::utils::serde::{
14    ByteReader,
15    ByteWriter,
16    Deserializable,
17    DeserializationError,
18    Serializable,
19};
20
21const MAGIC: &str = "note";
22
23// NOTE FILE
24// ================================================================================================
25
26/// A serialized representation of a note.
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub enum NoteFile {
29    /// The note's details aren't known.
30    NoteId(NoteId),
31    /// The note may or may not have already been recorded on chain.
32    ///
33    /// The `after_block_num` specifies the block after which the note is expected to appear on
34    /// chain. Though this should be treated as a hint (i.e., there is no guarantee that the note
35    /// will appear on chain or that it will in fact appear after the specified block).
36    ///
37    /// An optional tag specifies the tag associated with the note, though this also should be
38    /// treated as a hint.
39    NoteDetails {
40        details: NoteDetails,
41        after_block_num: BlockNumber,
42        tag: Option<NoteTag>,
43    },
44    /// The note has been recorded on chain.
45    NoteWithProof(Note, NoteInclusionProof),
46}
47
48#[cfg(feature = "std")]
49impl NoteFile {
50    /// Serializes and writes binary [NoteFile] to specified file
51    pub fn write(&self, filepath: impl AsRef<Path>) -> io::Result<()> {
52        fs::write(filepath, self.to_bytes())
53    }
54
55    /// Reads from file and tries to deserialize an [NoteFile]
56    pub fn read(filepath: impl AsRef<Path>) -> io::Result<Self> {
57        let mut file = File::open(filepath)?;
58        let mut buffer = Vec::new();
59
60        file.read_to_end(&mut buffer)?;
61        let mut reader = SliceReader::new(&buffer);
62
63        Ok(NoteFile::read_from(&mut reader).map_err(|_| io::ErrorKind::InvalidData)?)
64    }
65}
66
67impl From<NoteDetails> for NoteFile {
68    fn from(details: NoteDetails) -> Self {
69        NoteFile::NoteDetails {
70            details,
71            after_block_num: 0.into(),
72            tag: None,
73        }
74    }
75}
76
77impl From<NoteId> for NoteFile {
78    fn from(note_id: NoteId) -> Self {
79        NoteFile::NoteId(note_id)
80    }
81}
82
83// SERIALIZATION
84// ================================================================================================
85
86impl Serializable for NoteFile {
87    fn write_into<W: ByteWriter>(&self, target: &mut W) {
88        target.write_bytes(MAGIC.as_bytes());
89        match self {
90            NoteFile::NoteId(note_id) => {
91                target.write_u8(0);
92                note_id.write_into(target);
93            },
94            NoteFile::NoteDetails { details, after_block_num, tag } => {
95                target.write_u8(1);
96                details.write_into(target);
97                after_block_num.write_into(target);
98                tag.write_into(target);
99            },
100            NoteFile::NoteWithProof(note, proof) => {
101                target.write_u8(2);
102                note.write_into(target);
103                proof.write_into(target);
104            },
105        }
106    }
107}
108
109impl Deserializable for NoteFile {
110    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
111        let magic_value = source.read_string(4)?;
112        if magic_value != MAGIC {
113            return Err(DeserializationError::InvalidValue(format!(
114                "invalid note file marker: {magic_value}"
115            )));
116        }
117        match source.read_u8()? {
118            0 => Ok(NoteFile::NoteId(NoteId::read_from(source)?)),
119            1 => {
120                let details = NoteDetails::read_from(source)?;
121                let after_block_num = BlockNumber::read_from(source)?;
122                let tag = Option::<NoteTag>::read_from(source)?;
123                Ok(NoteFile::NoteDetails { details, after_block_num, tag })
124            },
125            2 => {
126                let note = Note::read_from(source)?;
127                let proof = NoteInclusionProof::read_from(source)?;
128                Ok(NoteFile::NoteWithProof(note, proof))
129            },
130            v => {
131                Err(DeserializationError::InvalidValue(format!("unknown variant {v} for NoteFile")))
132            },
133        }
134    }
135}
136
137// TESTS
138// ================================================================================================
139
140#[cfg(test)]
141mod tests {
142    use alloc::vec::Vec;
143
144    use crate::Word;
145    use crate::account::AccountId;
146    use crate::asset::{Asset, FungibleAsset};
147    use crate::block::BlockNumber;
148    use crate::note::{
149        Note,
150        NoteAssets,
151        NoteFile,
152        NoteInclusionProof,
153        NoteMetadata,
154        NoteRecipient,
155        NoteScript,
156        NoteStorage,
157        NoteTag,
158        NoteType,
159    };
160    use crate::testing::account_id::{
161        ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
162        ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
163    };
164    use crate::utils::serde::{Deserializable, Serializable};
165
166    fn create_example_note() -> Note {
167        let faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
168        let target =
169            AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
170
171        let serial_num = Word::from([0, 1, 2, 3u32]);
172        let script = NoteScript::mock();
173        let note_storage = NoteStorage::new(vec![target.prefix().into()]).unwrap();
174        let recipient = NoteRecipient::new(serial_num, script, note_storage);
175
176        let asset = Asset::Fungible(FungibleAsset::new(faucet, 100).unwrap());
177        let metadata = NoteMetadata::new(faucet, NoteType::Public).with_tag(NoteTag::from(123));
178
179        Note::new(NoteAssets::new(vec![asset]).unwrap(), metadata, recipient)
180    }
181
182    #[test]
183    fn serialized_note_magic() {
184        let note = create_example_note();
185        let file = NoteFile::NoteId(note.id());
186        let mut buffer = Vec::new();
187        file.write_into(&mut buffer);
188
189        let magic_value = &buffer[..4];
190        assert_eq!(magic_value, b"note");
191    }
192
193    #[test]
194    fn serialize_id() {
195        let note = create_example_note();
196        let file = NoteFile::NoteId(note.id());
197        let mut buffer = Vec::new();
198        file.write_into(&mut buffer);
199
200        let file_copy = NoteFile::read_from_bytes(&buffer).unwrap();
201
202        match file_copy {
203            NoteFile::NoteId(note_id) => {
204                assert_eq!(note.id(), note_id);
205            },
206            _ => panic!("Invalid note file variant"),
207        }
208    }
209
210    #[test]
211    fn serialize_details() {
212        let note = create_example_note();
213        let file = NoteFile::NoteDetails {
214            details: note.details.clone(),
215            after_block_num: 456.into(),
216            tag: Some(NoteTag::from(123)),
217        };
218        let mut buffer = Vec::new();
219        file.write_into(&mut buffer);
220
221        let file_copy = NoteFile::read_from_bytes(&buffer).unwrap();
222
223        match file_copy {
224            NoteFile::NoteDetails { details, after_block_num, tag } => {
225                assert_eq!(details, note.details);
226                assert_eq!(after_block_num, 456.into());
227                assert_eq!(tag, Some(NoteTag::from(123)));
228            },
229            _ => panic!("Invalid note file variant"),
230        }
231    }
232
233    #[test]
234    fn serialize_with_proof() {
235        let note = create_example_note();
236        let mock_inclusion_proof =
237            NoteInclusionProof::new(BlockNumber::from(0), 0, Default::default()).unwrap();
238        let file = NoteFile::NoteWithProof(note.clone(), mock_inclusion_proof.clone());
239        let mut buffer = Vec::new();
240        file.write_into(&mut buffer);
241
242        let file_copy = NoteFile::read_from_bytes(&buffer).unwrap();
243
244        match file_copy {
245            NoteFile::NoteWithProof(note_copy, inclusion_proof_copy) => {
246                assert_eq!(note, note_copy);
247                assert_eq!(inclusion_proof_copy, mock_inclusion_proof);
248            },
249            _ => panic!("Invalid note file variant"),
250        }
251    }
252}