use alloc::collections::BTreeSet;
use alloc::string::ToString;
use alloc::vec::Vec;
use core::fmt::Debug;
use crate::account::AccountHeader;
use crate::asset::FungibleAsset;
use crate::block::BlockNumber;
use crate::note::{
Note,
NoteAssets,
NoteHeader,
NoteId,
NoteMetadata,
NoteRecipient,
PartialNote,
compute_note_commitment,
};
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use crate::{Felt, Hasher, MAX_OUTPUT_NOTES_PER_TX, TransactionOutputError, Word};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransactionOutputs {
pub account: AccountHeader,
pub account_delta_commitment: Word,
pub output_notes: OutputNotes,
pub fee: FungibleAsset,
pub expiration_block_num: BlockNumber,
}
impl Serializable for TransactionOutputs {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.account.write_into(target);
self.account_delta_commitment.write_into(target);
self.output_notes.write_into(target);
self.fee.write_into(target);
self.expiration_block_num.write_into(target);
}
}
impl Deserializable for TransactionOutputs {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let account = AccountHeader::read_from(source)?;
let account_delta_commitment = Word::read_from(source)?;
let output_notes = OutputNotes::read_from(source)?;
let fee = FungibleAsset::read_from(source)?;
let expiration_block_num = BlockNumber::read_from(source)?;
Ok(Self {
account,
account_delta_commitment,
output_notes,
fee,
expiration_block_num,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OutputNotes {
notes: Vec<OutputNote>,
commitment: Word,
}
impl OutputNotes {
pub fn new(notes: Vec<OutputNote>) -> Result<Self, TransactionOutputError> {
if notes.len() > MAX_OUTPUT_NOTES_PER_TX {
return Err(TransactionOutputError::TooManyOutputNotes(notes.len()));
}
let mut seen_notes = BTreeSet::new();
for note in notes.iter() {
if !seen_notes.insert(note.id()) {
return Err(TransactionOutputError::DuplicateOutputNote(note.id()));
}
}
let commitment = Self::compute_commitment(notes.iter().map(NoteHeader::from));
Ok(Self { notes, commitment })
}
pub fn commitment(&self) -> Word {
self.commitment
}
pub fn num_notes(&self) -> usize {
self.notes.len()
}
pub fn is_empty(&self) -> bool {
self.notes.is_empty()
}
pub fn get_note(&self, idx: usize) -> &OutputNote {
&self.notes[idx]
}
pub fn iter(&self) -> impl Iterator<Item = &OutputNote> {
self.notes.iter()
}
pub(crate) fn compute_commitment(notes: impl ExactSizeIterator<Item = NoteHeader>) -> Word {
if notes.len() == 0 {
return Word::empty();
}
let mut elements: Vec<Felt> = Vec::with_capacity(notes.len() * 8);
for note_header in notes {
elements.extend_from_slice(note_header.id().as_elements());
elements.extend_from_slice(Word::from(note_header.metadata()).as_elements());
}
Hasher::hash_elements(&elements)
}
}
impl Serializable for OutputNotes {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
assert!(self.notes.len() <= u16::MAX.into());
target.write_u16(self.notes.len() as u16);
target.write_many(&self.notes);
}
}
impl Deserializable for OutputNotes {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let num_notes = source.read_u16()?;
let notes = source.read_many::<OutputNote>(num_notes.into())?;
Self::new(notes).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
}
}
const FULL: u8 = 0;
const PARTIAL: u8 = 1;
const HEADER: u8 = 2;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OutputNote {
Full(Note),
Partial(PartialNote),
Header(NoteHeader),
}
impl OutputNote {
pub fn assets(&self) -> Option<&NoteAssets> {
match self {
OutputNote::Full(note) => Some(note.assets()),
OutputNote::Partial(note) => Some(note.assets()),
OutputNote::Header(_) => None,
}
}
pub fn id(&self) -> NoteId {
match self {
OutputNote::Full(note) => note.id(),
OutputNote::Partial(note) => note.id(),
OutputNote::Header(note) => note.id(),
}
}
pub fn recipient(&self) -> Option<&NoteRecipient> {
match self {
OutputNote::Full(note) => Some(note.recipient()),
OutputNote::Partial(_) => None,
OutputNote::Header(_) => None,
}
}
pub fn recipient_digest(&self) -> Option<Word> {
match self {
OutputNote::Full(note) => Some(note.recipient().digest()),
OutputNote::Partial(note) => Some(note.recipient_digest()),
OutputNote::Header(_) => None,
}
}
pub fn metadata(&self) -> &NoteMetadata {
match self {
OutputNote::Full(note) => note.metadata(),
OutputNote::Partial(note) => note.metadata(),
OutputNote::Header(note) => note.metadata(),
}
}
pub fn shrink(&self) -> Self {
match self {
OutputNote::Full(note) if note.metadata().is_private() => {
OutputNote::Header(*note.header())
},
OutputNote::Partial(note) => OutputNote::Header(note.into()),
_ => self.clone(),
}
}
pub fn commitment(&self) -> Word {
compute_note_commitment(self.id(), self.metadata())
}
}
impl From<OutputNote> for NoteHeader {
fn from(value: OutputNote) -> Self {
(&value).into()
}
}
impl From<&OutputNote> for NoteHeader {
fn from(value: &OutputNote) -> Self {
match value {
OutputNote::Full(note) => note.into(),
OutputNote::Partial(note) => note.into(),
OutputNote::Header(note) => *note,
}
}
}
impl Serializable for OutputNote {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
match self {
OutputNote::Full(note) => {
target.write(FULL);
target.write(note);
},
OutputNote::Partial(note) => {
target.write(PARTIAL);
target.write(note);
},
OutputNote::Header(note) => {
target.write(HEADER);
target.write(note);
},
}
}
}
impl Deserializable for OutputNote {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
match source.read_u8()? {
FULL => Ok(OutputNote::Full(Note::read_from(source)?)),
PARTIAL => Ok(OutputNote::Partial(PartialNote::read_from(source)?)),
HEADER => Ok(OutputNote::Header(NoteHeader::read_from(source)?)),
v => Err(DeserializationError::InvalidValue(format!("invalid note type: {v}"))),
}
}
}
#[cfg(test)]
mod output_notes_tests {
use assert_matches::assert_matches;
use super::OutputNotes;
use crate::note::Note;
use crate::transaction::OutputNote;
use crate::{TransactionOutputError, Word};
#[test]
fn test_duplicate_output_notes() -> anyhow::Result<()> {
let mock_note = Note::mock_noop(Word::empty());
let mock_note_id = mock_note.id();
let mock_note_clone = mock_note.clone();
let error =
OutputNotes::new(vec![OutputNote::Full(mock_note), OutputNote::Full(mock_note_clone)])
.expect_err("input notes creation should fail");
assert_matches!(error, TransactionOutputError::DuplicateOutputNote(note_id) if note_id == mock_note_id);
Ok(())
}
}