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