1#[cfg(feature = "std")]
2use std::{
3 fs::{self, File},
4 io::{self, Read},
5 path::Path,
6 vec::Vec,
7};
8
9use miden_protocol::block::BlockNumber;
10use miden_protocol::note::{Note, NoteDetails, NoteId, NoteInclusionProof, NoteTag};
11#[cfg(feature = "std")]
12use miden_protocol::utils::serde::SliceReader;
13use miden_protocol::utils::serde::{
14 ByteReader,
15 ByteWriter,
16 Deserializable,
17 DeserializationError,
18 Serializable,
19};
20
21const MAGIC: &str = "note";
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub struct NoteSyncHint {
35 after_block_num: BlockNumber,
40 tag: NoteTag,
42}
43
44impl NoteSyncHint {
45 pub fn new(after_block_num: BlockNumber, tag: NoteTag) -> Self {
47 Self { after_block_num, tag }
48 }
49
50 pub fn after_block_num(&self) -> BlockNumber {
52 self.after_block_num
53 }
54
55 pub fn tag(&self) -> NoteTag {
57 self.tag
58 }
59}
60
61#[derive(Clone, Debug, PartialEq, Eq)]
69#[allow(clippy::large_enum_variant)]
70pub enum NoteFile {
71 NoteId(NoteId),
75 ExpectedNote {
91 details: NoteDetails,
92 sync_hint: NoteSyncHint,
93 },
94 Committed { note: Note, proof: NoteInclusionProof },
96}
97
98#[cfg(feature = "std")]
99impl NoteFile {
100 pub fn write(&self, filepath: impl AsRef<Path>) -> io::Result<()> {
102 fs::write(filepath, self.to_bytes())
103 }
104
105 pub fn read(filepath: impl AsRef<Path>) -> io::Result<Self> {
107 let mut file = File::open(filepath)?;
108 let mut buffer = Vec::new();
109
110 file.read_to_end(&mut buffer)?;
111 let mut reader = SliceReader::new(&buffer);
112
113 Ok(NoteFile::read_from(&mut reader).map_err(|_| io::ErrorKind::InvalidData)?)
114 }
115}
116
117impl From<Note> for NoteFile {
118 fn from(note: Note) -> Self {
119 let (assets, metadata, recipient, _attachments) = note.into_parts();
120 NoteFile::ExpectedNote {
121 details: NoteDetails::new(assets, recipient),
122 sync_hint: NoteSyncHint::new(0.into(), metadata.tag()),
123 }
124 }
125}
126
127impl From<NoteId> for NoteFile {
128 fn from(note_id: NoteId) -> Self {
129 NoteFile::NoteId(note_id)
130 }
131}
132
133impl Serializable for NoteFile {
137 fn write_into<W: ByteWriter>(&self, target: &mut W) {
138 target.write_bytes(MAGIC.as_bytes());
139 match self {
140 NoteFile::NoteId(note_id) => {
141 target.write_u8(0);
142 note_id.write_into(target);
143 },
144 NoteFile::ExpectedNote { details, sync_hint } => {
145 target.write_u8(1);
146 details.write_into(target);
147 sync_hint.write_into(target);
148 },
149 NoteFile::Committed { note, proof } => {
150 target.write_u8(2);
151 note.write_into(target);
152 proof.write_into(target);
153 },
154 }
155 }
156}
157
158impl Serializable for NoteSyncHint {
159 fn write_into<W: ByteWriter>(&self, target: &mut W) {
160 self.after_block_num.write_into(target);
161 self.tag.write_into(target);
162 }
163
164 fn get_size_hint(&self) -> usize {
165 self.after_block_num.get_size_hint() + self.tag.get_size_hint()
166 }
167}
168
169impl Deserializable for NoteFile {
170 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
171 let magic_value = source.read_string(4)?;
172 if magic_value != MAGIC {
173 return Err(DeserializationError::InvalidValue(format!(
174 "invalid note file marker: {magic_value}"
175 )));
176 }
177 match source.read_u8()? {
178 0 => Ok(NoteFile::NoteId(NoteId::read_from(source)?)),
179 1 => {
180 let details = NoteDetails::read_from(source)?;
181 let sync_hint = NoteSyncHint::read_from(source)?;
182 Ok(NoteFile::ExpectedNote { details, sync_hint })
183 },
184 2 => {
185 let note = Note::read_from(source)?;
186 let proof = NoteInclusionProof::read_from(source)?;
187 Ok(NoteFile::Committed { note, proof })
188 },
189 v => {
190 Err(DeserializationError::InvalidValue(format!("unknown variant {v} for NoteFile")))
191 },
192 }
193 }
194}
195
196impl Deserializable for NoteSyncHint {
197 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
198 let after_block_num = BlockNumber::read_from(source)?;
199 let tag = NoteTag::read_from(source)?;
200 Ok(Self { after_block_num, tag })
201 }
202}
203
204#[cfg(test)]
208mod tests {
209 use alloc::vec::Vec;
210
211 use miden_protocol::Word;
212 use miden_protocol::account::AccountId;
213 use miden_protocol::asset::{Asset, FungibleAsset};
214 use miden_protocol::block::BlockNumber;
215 use miden_protocol::note::{
216 Note,
217 NoteAssets,
218 NoteDetails,
219 NoteInclusionProof,
220 NoteRecipient,
221 NoteScript,
222 NoteStorage,
223 NoteTag,
224 NoteType,
225 PartialNoteMetadata,
226 };
227 use miden_protocol::testing::account_id::{
228 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
229 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
230 };
231 use miden_protocol::utils::serde::{Deserializable, Serializable};
232
233 use super::{NoteFile, NoteSyncHint};
234
235 fn create_example_note() -> Note {
236 let faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
237 let target =
238 AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
239
240 let serial_num = Word::from([0, 1, 2, 3u32]);
241 let script = NoteScript::mock();
242 let note_storage = NoteStorage::new(vec![target.prefix().into()]).unwrap();
243 let recipient = NoteRecipient::new(serial_num, script, note_storage);
244
245 let asset = Asset::Fungible(FungibleAsset::new(faucet, 100).unwrap());
246 let metadata =
247 PartialNoteMetadata::new(faucet, NoteType::Public).with_tag(NoteTag::from(123));
248
249 Note::new(NoteAssets::new(vec![asset]).unwrap(), metadata, recipient)
250 }
251
252 #[test]
253 fn serialized_note_magic() {
254 let note = create_example_note();
255 let file = NoteFile::NoteId(note.id());
256 let mut buffer = Vec::new();
257 file.write_into(&mut buffer);
258
259 let magic_value = &buffer[..4];
260 assert_eq!(magic_value, b"note");
261 }
262
263 fn assert_roundtrip(file: NoteFile) {
265 assert_eq!(NoteFile::read_from_bytes(&file.to_bytes()).unwrap(), file);
266 }
267
268 #[test]
269 fn serialize_id() {
270 assert_roundtrip(NoteFile::NoteId(create_example_note().id()));
271 }
272
273 #[test]
274 fn serialize_expected_note() {
275 let note = create_example_note();
276 assert_roundtrip(NoteFile::ExpectedNote {
277 details: NoteDetails::from(¬e),
278 sync_hint: NoteSyncHint::new(456.into(), NoteTag::from(123)),
279 });
280 }
281
282 #[test]
283 fn serialize_committed_note() {
284 let note = create_example_note();
285 let proof = NoteInclusionProof::new(BlockNumber::from(0), 0, Default::default()).unwrap();
286 assert_roundtrip(NoteFile::Committed { note, proof });
287 }
288}