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#[derive(Clone, Debug, PartialEq, Eq)]
28pub enum NoteFile {
29 NoteId(NoteId),
31 NoteDetails {
40 details: NoteDetails,
41 after_block_num: BlockNumber,
42 tag: Option<NoteTag>,
43 },
44 NoteWithProof(Note, NoteInclusionProof),
46}
47
48#[cfg(feature = "std")]
49impl NoteFile {
50 pub fn write(&self, filepath: impl AsRef<Path>) -> io::Result<()> {
52 fs::write(filepath, self.to_bytes())
53 }
54
55 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
83impl 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#[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}