miden_objects/transaction/
outputs.rs1use alloc::collections::BTreeSet;
2use alloc::string::ToString;
3use alloc::vec::Vec;
4use core::fmt::Debug;
5
6use crate::account::AccountHeader;
7use crate::asset::FungibleAsset;
8use crate::block::BlockNumber;
9use crate::note::{
10 Note,
11 NoteAssets,
12 NoteHeader,
13 NoteId,
14 NoteMetadata,
15 NoteRecipient,
16 PartialNote,
17 compute_note_commitment,
18};
19use crate::utils::serde::{
20 ByteReader,
21 ByteWriter,
22 Deserializable,
23 DeserializationError,
24 Serializable,
25};
26use crate::{Felt, Hasher, MAX_OUTPUT_NOTES_PER_TX, TransactionOutputError, Word};
27
28#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct TransactionOutputs {
34 pub account: AccountHeader,
36 pub account_delta_commitment: Word,
38 pub output_notes: OutputNotes,
40 pub fee: FungibleAsset,
42 pub expiration_block_num: BlockNumber,
44}
45
46impl Serializable for TransactionOutputs {
47 fn write_into<W: ByteWriter>(&self, target: &mut W) {
48 self.account.write_into(target);
49 self.account_delta_commitment.write_into(target);
50 self.output_notes.write_into(target);
51 self.fee.write_into(target);
52 self.expiration_block_num.write_into(target);
53 }
54}
55
56impl Deserializable for TransactionOutputs {
57 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
58 let account = AccountHeader::read_from(source)?;
59 let account_delta_commitment = Word::read_from(source)?;
60 let output_notes = OutputNotes::read_from(source)?;
61 let fee = FungibleAsset::read_from(source)?;
62 let expiration_block_num = BlockNumber::read_from(source)?;
63
64 Ok(Self {
65 account,
66 account_delta_commitment,
67 output_notes,
68 fee,
69 expiration_block_num,
70 })
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct OutputNotes {
81 notes: Vec<OutputNote>,
82 commitment: Word,
83}
84
85impl OutputNotes {
86 pub fn new(notes: Vec<OutputNote>) -> Result<Self, TransactionOutputError> {
95 if notes.len() > MAX_OUTPUT_NOTES_PER_TX {
96 return Err(TransactionOutputError::TooManyOutputNotes(notes.len()));
97 }
98
99 let mut seen_notes = BTreeSet::new();
100 for note in notes.iter() {
101 if !seen_notes.insert(note.id()) {
102 return Err(TransactionOutputError::DuplicateOutputNote(note.id()));
103 }
104 }
105
106 let commitment = Self::compute_commitment(notes.iter().map(NoteHeader::from));
107
108 Ok(Self { notes, commitment })
109 }
110
111 pub fn commitment(&self) -> Word {
119 self.commitment
120 }
121 pub fn num_notes(&self) -> usize {
123 self.notes.len()
124 }
125
126 pub fn is_empty(&self) -> bool {
128 self.notes.is_empty()
129 }
130
131 pub fn get_note(&self, idx: usize) -> &OutputNote {
133 &self.notes[idx]
134 }
135
136 pub fn iter(&self) -> impl Iterator<Item = &OutputNote> {
141 self.notes.iter()
142 }
143
144 pub(crate) fn compute_commitment(notes: impl ExactSizeIterator<Item = NoteHeader>) -> Word {
152 if notes.len() == 0 {
153 return Word::empty();
154 }
155
156 let mut elements: Vec<Felt> = Vec::with_capacity(notes.len() * 8);
157 for note_header in notes {
158 elements.extend_from_slice(note_header.id().as_elements());
159 elements.extend_from_slice(Word::from(note_header.metadata()).as_elements());
160 }
161
162 Hasher::hash_elements(&elements)
163 }
164}
165
166impl Serializable for OutputNotes {
170 fn write_into<W: ByteWriter>(&self, target: &mut W) {
171 assert!(self.notes.len() <= u16::MAX.into());
173 target.write_u16(self.notes.len() as u16);
174 target.write_many(&self.notes);
175 }
176}
177
178impl Deserializable for OutputNotes {
179 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
180 let num_notes = source.read_u16()?;
181 let notes = source.read_many::<OutputNote>(num_notes.into())?;
182 Self::new(notes).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
183 }
184}
185
186const FULL: u8 = 0;
190const PARTIAL: u8 = 1;
191const HEADER: u8 = 2;
192
193#[derive(Debug, Clone, PartialEq, Eq)]
195pub enum OutputNote {
196 Full(Note),
197 Partial(PartialNote),
198 Header(NoteHeader),
199}
200
201impl OutputNote {
202 pub fn assets(&self) -> Option<&NoteAssets> {
204 match self {
205 OutputNote::Full(note) => Some(note.assets()),
206 OutputNote::Partial(note) => Some(note.assets()),
207 OutputNote::Header(_) => None,
208 }
209 }
210
211 pub fn id(&self) -> NoteId {
215 match self {
216 OutputNote::Full(note) => note.id(),
217 OutputNote::Partial(note) => note.id(),
218 OutputNote::Header(note) => note.id(),
219 }
220 }
221
222 pub fn recipient(&self) -> Option<&NoteRecipient> {
227 match self {
228 OutputNote::Full(note) => Some(note.recipient()),
229 OutputNote::Partial(_) => None,
230 OutputNote::Header(_) => None,
231 }
232 }
233
234 pub fn recipient_digest(&self) -> Option<Word> {
240 match self {
241 OutputNote::Full(note) => Some(note.recipient().digest()),
242 OutputNote::Partial(note) => Some(note.recipient_digest()),
243 OutputNote::Header(_) => None,
244 }
245 }
246
247 pub fn metadata(&self) -> &NoteMetadata {
249 match self {
250 OutputNote::Full(note) => note.metadata(),
251 OutputNote::Partial(note) => note.metadata(),
252 OutputNote::Header(note) => note.metadata(),
253 }
254 }
255
256 pub fn shrink(&self) -> Self {
262 match self {
263 OutputNote::Full(note) if note.metadata().is_private() => {
264 OutputNote::Header(*note.header())
265 },
266 OutputNote::Partial(note) => OutputNote::Header(note.into()),
267 _ => self.clone(),
268 }
269 }
270
271 pub fn commitment(&self) -> Word {
275 compute_note_commitment(self.id(), self.metadata())
276 }
277}
278
279impl From<OutputNote> for NoteHeader {
283 fn from(value: OutputNote) -> Self {
284 (&value).into()
285 }
286}
287
288impl From<&OutputNote> for NoteHeader {
289 fn from(value: &OutputNote) -> Self {
290 match value {
291 OutputNote::Full(note) => note.into(),
292 OutputNote::Partial(note) => note.into(),
293 OutputNote::Header(note) => *note,
294 }
295 }
296}
297
298impl Serializable for OutputNote {
302 fn write_into<W: ByteWriter>(&self, target: &mut W) {
303 match self {
304 OutputNote::Full(note) => {
305 target.write(FULL);
306 target.write(note);
307 },
308 OutputNote::Partial(note) => {
309 target.write(PARTIAL);
310 target.write(note);
311 },
312 OutputNote::Header(note) => {
313 target.write(HEADER);
314 target.write(note);
315 },
316 }
317 }
318}
319
320impl Deserializable for OutputNote {
321 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
322 match source.read_u8()? {
323 FULL => Ok(OutputNote::Full(Note::read_from(source)?)),
324 PARTIAL => Ok(OutputNote::Partial(PartialNote::read_from(source)?)),
325 HEADER => Ok(OutputNote::Header(NoteHeader::read_from(source)?)),
326 v => Err(DeserializationError::InvalidValue(format!("invalid note type: {v}"))),
327 }
328 }
329}
330
331#[cfg(test)]
335mod output_notes_tests {
336 use assert_matches::assert_matches;
337
338 use super::OutputNotes;
339 use crate::note::Note;
340 use crate::transaction::OutputNote;
341 use crate::{TransactionOutputError, Word};
342
343 #[test]
344 fn test_duplicate_output_notes() -> anyhow::Result<()> {
345 let mock_note = Note::mock_noop(Word::empty());
346 let mock_note_id = mock_note.id();
347 let mock_note_clone = mock_note.clone();
348
349 let error =
350 OutputNotes::new(vec![OutputNote::Full(mock_note), OutputNote::Full(mock_note_clone)])
351 .expect_err("input notes creation should fail");
352
353 assert_matches!(error, TransactionOutputError::DuplicateOutputNote(note_id) if note_id == mock_note_id);
354
355 Ok(())
356 }
357}