From 0a29e02798adf87be4419b9ab10ba0e12aab7ca1 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Dec 2023 02:28:08 -0800 Subject: [PATCH] refactor: consolidate transaction output structs in a single module --- miden-lib/src/notes/mod.rs | 7 +- miden-tx/src/executor/mod.rs | 10 +- miden-tx/src/prover/mod.rs | 12 +- miden-tx/src/result.rs | 15 +- miden-tx/src/tests.rs | 6 +- miden-tx/tests/faucet_contract_test.rs | 12 +- miden-tx/tests/swap_script_test.rs | 9 +- objects/src/errors.rs | 8 +- objects/src/notes/mod.rs | 3 - objects/src/transaction/created_notes.rs | 84 --------- objects/src/transaction/inputs.rs | 10 +- objects/src/transaction/mod.rs | 10 +- objects/src/transaction/outputs.rs | 198 ++++++++++++++++++++++ objects/src/transaction/transaction_id.rs | 4 +- objects/src/transaction/tx_result.rs | 27 +-- objects/src/transaction/tx_witness.rs | 37 ++-- 16 files changed, 286 insertions(+), 166 deletions(-) delete mode 100644 objects/src/transaction/created_notes.rs create mode 100644 objects/src/transaction/outputs.rs diff --git a/miden-lib/src/notes/mod.rs b/miden-lib/src/notes/mod.rs index 35e55701f..fe647b3dd 100644 --- a/miden-lib/src/notes/mod.rs +++ b/miden-lib/src/notes/mod.rs @@ -2,7 +2,8 @@ use miden_objects::{ accounts::AccountId, assembly::ProgramAst, assets::Asset, - notes::{Note, NoteMetadata, NoteScript, NoteStub, NoteVault}, + notes::{Note, NoteMetadata, NoteScript, NoteVault}, + transaction::OutputNote, utils::{collections::Vec, vec}, Digest, Felt, Hasher, NoteError, StarkField, Word, WORD_SIZE, ZERO, }; @@ -80,7 +81,7 @@ pub fn create_note( Note::new(note_script.clone(), &inputs, &assets, serial_num, sender, tag.unwrap_or(ZERO)) } -pub fn notes_try_from_elements(elements: &[Word]) -> Result { +pub fn notes_try_from_elements(elements: &[Word]) -> Result { if elements.len() < CREATED_NOTE_CORE_DATA_SIZE { return Err(NoteError::InvalidStubDataLen(elements.len())); } @@ -104,7 +105,7 @@ pub fn notes_try_from_elements(elements: &[Word]) -> Result return Err(NoteError::InconsistentStubVaultHash(vault_hash, vault.hash())); } - let stub = NoteStub::new(recipient, vault, metadata)?; + let stub = OutputNote::new(recipient, vault, metadata); if stub.hash() != hash { return Err(NoteError::InconsistentStubHash(stub.hash(), hash)); } diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index 6c0ee771b..2e8557715 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -2,7 +2,7 @@ use miden_lib::{outputs::TX_SCRIPT_ROOT_WORD_IDX, transaction::extract_account_s use miden_objects::{ accounts::{Account, AccountDelta}, assembly::ProgramAst, - transaction::{CreatedNotes, FinalAccountStub, InputNotes, TransactionScript}, + transaction::{FinalAccountStub, InputNotes, OutputNotes, TransactionScript}, Felt, TransactionResultError, Word, WORD_SIZE, }; use vm_core::{Program, StackOutputs, StarkField}; @@ -193,7 +193,7 @@ impl TransactionExecutor { /// Creates a new [TransactionResult] from the provided data, advice provider and stack outputs. pub fn create_transaction_result( initial_account: Account, - consumed_notes: InputNotes, + input_notes: InputNotes, block_hash: Digest, program: Program, tx_script_root: Option, @@ -207,7 +207,7 @@ pub fn create_transaction_result( // parse transaction results let final_account_stub = FinalAccountStub::try_from_vm_result(&stack_outputs, &stack, &map, &store)?; - let created_notes = CreatedNotes::try_from_vm_result(&stack_outputs, &stack, &map, &store)?; + let output_notes = OutputNotes::try_from_vm_result(&stack_outputs, &stack, &map, &store)?; // assert the tx_script_root is consistent with the output stack debug_assert_eq!( @@ -243,8 +243,8 @@ pub fn create_transaction_result( initial_account, final_account_stub, account_delta, - consumed_notes, - created_notes, + input_notes, + output_notes, block_hash, program, tx_script_root, diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index e8fe87baf..a8a50597e 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -1,5 +1,5 @@ use miden_objects::transaction::{ - CreatedNotes, FinalAccountStub, PreparedTransaction, ProvenTransaction, TransactionWitness, + FinalAccountStub, OutputNotes, PreparedTransaction, ProvenTransaction, TransactionWitness, }; use miden_prover::prove; pub use miden_prover::ProvingOptions; @@ -50,7 +50,7 @@ impl TransactionProver { let final_account_stub = FinalAccountStub::try_from_vm_result(&outputs, &stack, &map, &store) .map_err(TransactionProverError::TransactionResultError)?; - let created_notes = CreatedNotes::try_from_vm_result(&outputs, &stack, &map, &store) + let created_notes = OutputNotes::try_from_vm_result(&outputs, &stack, &map, &store) .map_err(TransactionProverError::TransactionResultError)?; let (account, block_header, _chain, input_notes, _tx_program, tx_script) = @@ -61,7 +61,7 @@ impl TransactionProver { account.hash(), final_account_stub.0.hash(), input_notes.nullifiers().collect(), - created_notes.into(), + created_notes.envelopes().collect(), tx_script.map(|tx_script| *tx_script.hash()), block_header.hash(), proof, @@ -81,7 +81,7 @@ impl TransactionProver { // extract required data from the transaction witness let stack_inputs = tx_witness.get_stack_inputs(); let consumed_notes_info = tx_witness - .consumed_notes_info() + .input_notes_info() .map_err(TransactionProverError::CorruptTransactionWitnessConsumedNoteData)?; let ( account_id, @@ -105,7 +105,7 @@ impl TransactionProver { let final_account_stub = FinalAccountStub::try_from_vm_result(&outputs, &stack, &map, &store) .map_err(TransactionProverError::TransactionResultError)?; - let created_notes = CreatedNotes::try_from_vm_result(&outputs, &stack, &map, &store) + let created_notes = OutputNotes::try_from_vm_result(&outputs, &stack, &map, &store) .map_err(TransactionProverError::TransactionResultError)?; Ok(ProvenTransaction::new( @@ -113,7 +113,7 @@ impl TransactionProver { initial_account_hash, final_account_stub.0.hash(), consumed_notes_info, - created_notes.into(), + created_notes.envelopes().collect(), tx_script_root, block_hash, proof, diff --git a/miden-tx/src/result.rs b/miden-tx/src/result.rs index 05c33a0f2..fcd945924 100644 --- a/miden-tx/src/result.rs +++ b/miden-tx/src/result.rs @@ -6,8 +6,7 @@ use miden_lib::{ }; use miden_objects::{ crypto::merkle::MerkleStore, - notes::NoteStub, - transaction::{CreatedNotes, FinalAccountStub}, + transaction::{FinalAccountStub, OutputNote, OutputNotes}, utils::collections::{BTreeMap, Vec}, Digest, Felt, TransactionResultError, Word, WORD_SIZE, }; @@ -27,7 +26,7 @@ pub trait TryFromVmResult: Sized { ) -> Result; } -impl TryFromVmResult for CreatedNotes { +impl TryFromVmResult for OutputNotes { type Error = TransactionResultError; fn try_from_vm_result( @@ -50,22 +49,22 @@ impl TryFromVmResult for CreatedNotes { let created_notes_data = group_slice_elements::( advice_map .get(&created_notes_commitment.as_bytes()) - .ok_or(TransactionResultError::CreatedNoteDataNotFound)?, + .ok_or(TransactionResultError::OutputNoteDataNotFound)?, ); let mut created_notes = Vec::new(); let mut created_note_ptr = 0; while created_note_ptr < created_notes_data.len() { - let note_stub: NoteStub = + let note_stub: OutputNote = notes_try_from_elements(&created_notes_data[created_note_ptr..]) - .map_err(TransactionResultError::CreatedNoteDataInvalid)?; + .map_err(TransactionResultError::OutputNoteDataInvalid)?; created_notes.push(note_stub); created_note_ptr += NOTE_MEM_SIZE as usize; } - let created_notes = Self::new(created_notes); + let created_notes = Self::new(created_notes)?; if created_notes_commitment != created_notes.commitment() { - return Err(TransactionResultError::CreatedNotesCommitmentInconsistent( + return Err(TransactionResultError::OutputNotesCommitmentInconsistent( created_notes_commitment, created_notes.commitment(), )); diff --git a/miden-tx/src/tests.rs b/miden-tx/src/tests.rs index b0ca91cd4..9ef72fa77 100644 --- a/miden-tx/src/tests.rs +++ b/miden-tx/src/tests.rs @@ -3,7 +3,7 @@ use miden_objects::{ assembly::{Assembler, ModuleAst, ProgramAst}, assets::{Asset, FungibleAsset}, block::BlockHeader, - transaction::{ChainMmr, CreatedNotes, FinalAccountStub, InputNote, InputNotes}, + transaction::{ChainMmr, FinalAccountStub, InputNote, InputNotes, OutputNotes}, Felt, Word, }; use miden_prover::ProvingOptions; @@ -62,10 +62,10 @@ fn test_transaction_executor_witness() { let final_account_stub = FinalAccountStub::try_from_vm_result(result.stack_outputs(), &stack, &map, &store).unwrap(); let created_notes = - CreatedNotes::try_from_vm_result(result.stack_outputs(), &stack, &map, &store).unwrap(); + OutputNotes::try_from_vm_result(result.stack_outputs(), &stack, &map, &store).unwrap(); assert_eq!(transaction_result.final_account_hash(), final_account_stub.0.hash()); - assert_eq!(transaction_result.created_notes(), &created_notes); + assert_eq!(transaction_result.output_notes(), &created_notes); } #[test] diff --git a/miden-tx/tests/faucet_contract_test.rs b/miden-tx/tests/faucet_contract_test.rs index 31a67d420..c0533cde1 100644 --- a/miden-tx/tests/faucet_contract_test.rs +++ b/miden-tx/tests/faucet_contract_test.rs @@ -7,7 +7,8 @@ use miden_objects::{ assembly::{ModuleAst, ProgramAst}, assets::{Asset, FungibleAsset, TokenSymbol}, crypto::dsa::rpo_falcon512::{KeyPair, PublicKey}, - notes::{NoteMetadata, NoteStub, NoteVault}, + notes::{NoteMetadata, NoteVault}, + transaction::OutputNote, Felt, Word, ZERO, }; use miden_tx::TransactionExecutor; @@ -76,14 +77,13 @@ fn test_faucet_contract_mint_fungible_asset_succeeds() { let fungible_asset: Asset = FungibleAsset::new(faucet_account.id(), amount.into()).unwrap().into(); - let expected_note = NoteStub::new( + let expected_note = OutputNote::new( recipient.into(), NoteVault::new(&[fungible_asset]).unwrap(), NoteMetadata::new(faucet_account.id(), tag, Felt::new(1)), - ) - .unwrap(); + ); - let created_note = transaction_result.created_notes().notes()[0].clone(); + let created_note = transaction_result.output_notes().get_note(0).clone(); assert_eq!(created_note.recipient(), expected_note.recipient()); assert_eq!(created_note.vault(), expected_note.vault()); assert_eq!(created_note.metadata(), expected_note.metadata()); @@ -206,7 +206,7 @@ fn test_faucet_contract_burn_fungible_asset_succeeds() { // check that the account burned the asset assert_eq!(transaction_result.account_delta().nonce(), Some(Felt::new(2))); - assert_eq!(transaction_result.consumed_notes().get_note(0).note().hash(), note.hash()); + assert_eq!(transaction_result.input_notes().get_note(0).note().hash(), note.hash()); } #[test] diff --git a/miden-tx/tests/swap_script_test.rs b/miden-tx/tests/swap_script_test.rs index ddeb18271..b78bb5b69 100644 --- a/miden-tx/tests/swap_script_test.rs +++ b/miden-tx/tests/swap_script_test.rs @@ -6,7 +6,8 @@ use miden_objects::{ accounts::{Account, AccountId, AccountVault, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN}, assembly::ProgramAst, assets::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}, - notes::{NoteMetadata, NoteStub, NoteVault}, + notes::{NoteMetadata, NoteVault}, + transaction::OutputNote, Felt, }; use miden_tx::TransactionExecutor; @@ -102,7 +103,7 @@ fn test_swap_script() { assert_eq!(transaction_result.final_account_hash(), target_account_after.hash()); // Check if only one `Note` has been created - assert_eq!(transaction_result.created_notes().notes().len(), 1); + assert_eq!(transaction_result.output_notes().num_notes(), 1); // Check if the created `Note` is what we expect let recipient = Digest::new([ @@ -117,9 +118,9 @@ fn test_swap_script() { let note_vault = NoteVault::new(&[non_fungible_asset]).unwrap(); - let requested_note = NoteStub::new(recipient, note_vault, note_metadata).unwrap(); + let requested_note = OutputNote::new(recipient, note_vault, note_metadata); - let created_note = &transaction_result.created_notes().notes()[0]; + let created_note = transaction_result.output_notes().get_note(0); assert_eq!(created_note, &requested_note); } diff --git a/objects/src/errors.rs b/objects/src/errors.rs index adf222b65..13dfa38bb 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -346,15 +346,17 @@ impl std::error::Error for ExecutedTransactionError {} // ================================================================================================ #[derive(Debug)] pub enum TransactionResultError { - CreatedNoteDataNotFound, - CreatedNoteDataInvalid(NoteError), - CreatedNotesCommitmentInconsistent(Digest, Digest), + OutputNoteDataNotFound, + OutputNoteDataInvalid(NoteError), + OutputNotesCommitmentInconsistent(Digest, Digest), + DuplicateOutputNote(Digest), FinalAccountDataNotFound, FinalAccountStubDataInvalid(AccountError), InconsistentAccountCodeHash(Digest, Digest), ExtractAccountStorageSlotsDeltaFailed(MerkleError), ExtractAccountStorageStoreDeltaFailed(MerkleError), ExtractAccountVaultLeavesDeltaFailed(MerkleError), + TooManyOutputNotes { max: usize, actual: usize }, UpdatedAccountCodeInvalid(AccountError), } diff --git a/objects/src/notes/mod.rs b/objects/src/notes/mod.rs index 4f63da13e..d504bcf95 100644 --- a/objects/src/notes/mod.rs +++ b/objects/src/notes/mod.rs @@ -25,9 +25,6 @@ pub use origin::{NoteInclusionProof, NoteOrigin}; mod script; pub use script::NoteScript; -mod stub; -pub use stub::NoteStub; - mod vault; pub use vault::NoteVault; use vm_processor::DeserializationError; diff --git a/objects/src/transaction/created_notes.rs b/objects/src/transaction/created_notes.rs deleted file mode 100644 index 4329a8590..000000000 --- a/objects/src/transaction/created_notes.rs +++ /dev/null @@ -1,84 +0,0 @@ -use core::iter::FromIterator; - -use crate::{ - notes::{Note, NoteEnvelope, NoteStub}, - utils::collections::Vec, - Digest, Felt, Hasher, Word, -}; - -// CREATED NOTES -// ================================================================================================ -/// [CreatedNotes] represents the notes created by a transaction. -/// -/// [CreatedNotes] is composed of: -/// - notes: a vector of [NoteStub] objects representing the notes created by the transaction. -/// - commitment: a commitment to the created notes. -#[derive(Debug, Clone, PartialEq)] -pub struct CreatedNotes { - notes: Vec, - commitment: Digest, -} - -impl CreatedNotes { - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - /// Creates a new [CreatedNotes] object from the provided vector of [NoteStub]s. - pub fn new(notes: Vec) -> Self { - let commitment = generate_created_notes_stub_commitment(¬es); - Self { notes, commitment } - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - /// Returns a reference to the vector of [NoteStub]s. - pub fn notes(&self) -> &[NoteStub] { - &self.notes - } - - /// Returns the commitment to the created notes. - pub fn commitment(&self) -> Digest { - self.commitment - } -} - -/// Returns the created notes commitment. -/// This is a sequential hash of all (hash, metadata) pairs for the notes created in the transaction. -pub fn generate_created_notes_stub_commitment(notes: &[NoteStub]) -> Digest { - let mut elements: Vec = Vec::with_capacity(notes.len() * 8); - for note in notes.iter() { - elements.extend_from_slice(note.hash().as_elements()); - elements.extend_from_slice(&Word::from(note.metadata())); - } - - Hasher::hash_elements(&elements) -} - -impl From for Vec { - fn from(created_notes: CreatedNotes) -> Self { - (&created_notes).into() - } -} - -impl From<&CreatedNotes> for Vec { - fn from(created_notes: &CreatedNotes) -> Self { - created_notes.notes.iter().map(|note| note.into()).collect::>() - } -} - -impl From> for CreatedNotes { - fn from(notes: Vec) -> Self { - Self::new(notes.into_iter().map(|note| note.into()).collect()) - } -} - -impl From> for CreatedNotes { - fn from(notes: Vec<&Note>) -> Self { - Self::new(notes.iter().map(|note| (*note).into()).collect()) - } -} - -impl FromIterator for CreatedNotes { - fn from_iter>(iter: T) -> Self { - Self::new(iter.into_iter().map(|v| v.into()).collect()) - } -} diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index 4c8148687..ab8385b89 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -2,12 +2,12 @@ use core::cell::OnceCell; use super::{ Account, AdviceInputsBuilder, BlockHeader, ChainMmr, Digest, Felt, Hasher, Note, Nullifier, - ToAdviceInputs, Vec, Word, MAX_NOTES_PER_TRANSACTION, + ToAdviceInputs, Word, MAX_INPUT_NOTES_PER_TRANSACTION, }; use crate::{ notes::{NoteInclusionProof, NoteOrigin}, utils::{ - collections::{self, BTreeSet}, + collections::{self, BTreeSet, Vec}, serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, string::ToString, }, @@ -41,16 +41,16 @@ pub struct InputNotes { impl InputNotes { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- - /// Returns a new [InputNotes] instantiated from the provided vector of notes. + /// Returns new [InputNotes] instantiated from the provided vector of notes. /// /// # Errors /// Returns an error if: /// - The total number of notes is greater than 1024. /// - The vector of notes contains duplicates. pub fn new(notes: Vec) -> Result { - if notes.len() > MAX_NOTES_PER_TRANSACTION { + if notes.len() > MAX_INPUT_NOTES_PER_TRANSACTION { return Err(TransactionInputsError::TooManyInputNotes { - max: MAX_NOTES_PER_TRANSACTION, + max: MAX_INPUT_NOTES_PER_TRANSACTION, actual: notes.len(), }); } diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index 5935e9951..85c2218d8 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -10,10 +10,10 @@ use super::{ mod account_stub; mod chain_mmr; -mod created_notes; mod event; mod executed_tx; mod inputs; +mod outputs; mod prepared_tx; mod proven_tx; mod script; @@ -25,10 +25,10 @@ mod utils; pub use account_stub::FinalAccountStub; pub use chain_mmr::ChainMmr; -pub use created_notes::CreatedNotes; pub use event::Event; pub use executed_tx::ExecutedTransaction; pub use inputs::{InputNote, InputNotes, TransactionInputs}; +pub use outputs::{OutputNote, OutputNotes}; pub use prepared_tx::PreparedTransaction; pub use proven_tx::ProvenTransaction; pub use script::TransactionScript; @@ -42,4 +42,8 @@ pub mod utils; // CONSTANTS // ================================================================================================ -const MAX_NOTES_PER_TRANSACTION: usize = 1024; +/// Maximum number of notes consumed in a single transaction. +const MAX_INPUT_NOTES_PER_TRANSACTION: usize = 1024; + +/// Maximum number of notes created in a single transaction. +const MAX_OUTPUT_NOTES_PER_TRANSACTION: usize = 1024; diff --git a/objects/src/transaction/outputs.rs b/objects/src/transaction/outputs.rs new file mode 100644 index 000000000..cb5e2756b --- /dev/null +++ b/objects/src/transaction/outputs.rs @@ -0,0 +1,198 @@ +use core::cell::OnceCell; + +use super::MAX_OUTPUT_NOTES_PER_TRANSACTION; +use crate::{ + notes::{Note, NoteEnvelope, NoteMetadata, NoteVault}, + utils::collections::{self, BTreeSet, Vec}, + Digest, Felt, Hasher, StarkField, TransactionResultError, Word, +}; + +// OUTPUT NOTES +// ================================================================================================ + +/// Contains a list of output notes of a transaction. +#[derive(Debug, Clone)] +pub struct OutputNotes { + notes: Vec, + commitment: OnceCell, +} + +impl OutputNotes { + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + /// Returns new [OutputNotes] instantiated from the provide vector of notes. + /// + /// # Errors + /// Returns an error if: + /// - The total number of notes is greater than 1024. + /// - The vector of notes contains duplicates. + pub fn new(notes: Vec) -> Result { + if notes.len() > MAX_OUTPUT_NOTES_PER_TRANSACTION { + return Err(TransactionResultError::TooManyOutputNotes { + max: MAX_OUTPUT_NOTES_PER_TRANSACTION, + actual: notes.len(), + }); + } + + let mut seen_notes = BTreeSet::new(); + for note in notes.iter() { + if !seen_notes.insert(note.hash()) { + return Err(TransactionResultError::DuplicateOutputNote(note.hash())); + } + } + + Ok(Self { notes, commitment: OnceCell::new() }) + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the commitment to the output notes. + /// + /// The commitment is computed as a sequential hash of (hash, metadata) tuples for the notes + /// created in a transaction. + pub fn commitment(&self) -> Digest { + *self.commitment.get_or_init(|| build_input_notes_commitment(&self.notes)) + } + /// Returns total number of output notes. + pub fn num_notes(&self) -> usize { + self.notes.len() + } + + /// Returns true if this [OutputNotes] does not contain any notes. + pub fn is_empty(&self) -> bool { + self.notes.is_empty() + } + + /// Returns a reference to the [OutputNote] located at the specified index. + pub fn get_note(&self, idx: usize) -> &OutputNote { + &self.notes[idx] + } + + // ITERATORS + // -------------------------------------------------------------------------------------------- + + /// Returns an iterator over notes in this [OutputNote]. + pub fn iter(&self) -> impl Iterator { + self.notes.iter() + } + + /// Returns an iterator over envelopes of all notes in this [OutputNotes]. + pub fn envelopes(&self) -> impl Iterator + '_ { + self.notes.iter().map(|note| note.envelope) + } +} + +impl IntoIterator for OutputNotes { + type Item = OutputNote; + type IntoIter = collections::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.notes.into_iter() + } +} + +impl PartialEq for OutputNotes { + fn eq(&self, other: &Self) -> bool { + self.notes == other.notes + } +} + +impl Eq for OutputNotes {} + +// HELPER FUNCTIONS +// ------------------------------------------------------------------------------------------------ + +/// Build a commitment to output notes. +/// +/// The commitment is computed as a sequential hash of (hash, metadata) tuples for the notes +/// created in a transaction. +fn build_input_notes_commitment(notes: &[OutputNote]) -> Digest { + let mut elements: Vec = Vec::with_capacity(notes.len() * 8); + for note in notes.iter() { + elements.extend_from_slice(note.hash().as_elements()); + elements.extend_from_slice(&Word::from(note.metadata())); + } + + Hasher::hash_elements(&elements) +} + +// OUTPUT NOTE +// ================================================================================================ + +/// An note create during a transaction. +/// +/// When a note is produced in a transaction, the note's recipient, vault and metadata must be +/// known. However, other information about the note may or may not be know to the note's producer. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct OutputNote { + envelope: NoteEnvelope, + recipient: Digest, + vault: NoteVault, +} + +impl OutputNote { + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + /// Returns a new [OutputNote] instantiated from the provided parameters. + pub fn new(recipient: Digest, vault: NoteVault, metadata: NoteMetadata) -> Self { + // assert is OK here because we'll eventually remove `num_assets` from the metadata + assert_eq!(vault.num_assets() as u64, metadata.num_assets().as_int()); + + let hash = Hasher::merge(&[recipient, vault.hash()]); + Self { + envelope: NoteEnvelope::new(hash, metadata), + recipient, + vault, + } + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the recipient of the note. + pub fn recipient(&self) -> &Digest { + &self.recipient + } + + /// Returns a reference to the asset vault of this note. + pub fn vault(&self) -> &NoteVault { + &self.vault + } + + /// Returns the metadata associated with this note. + pub fn metadata(&self) -> &NoteMetadata { + self.envelope.metadata() + } + + /// Returns the hash of this note stub. + pub fn hash(&self) -> Digest { + self.envelope.note_hash() + } +} + +impl From for NoteEnvelope { + fn from(note_stub: OutputNote) -> Self { + note_stub.envelope + } +} + +impl From<&OutputNote> for NoteEnvelope { + fn from(note_stub: &OutputNote) -> Self { + note_stub.envelope + } +} + +impl From for OutputNote { + fn from(note: Note) -> Self { + (¬e).into() + } +} + +impl From<&Note> for OutputNote { + fn from(note: &Note) -> Self { + let recipient = note.recipient(); + Self::new(recipient, note.vault().clone(), *note.metadata()) + } +} diff --git a/objects/src/transaction/transaction_id.rs b/objects/src/transaction/transaction_id.rs index b314641fb..215431aab 100644 --- a/objects/src/transaction/transaction_id.rs +++ b/objects/src/transaction/transaction_id.rs @@ -80,8 +80,8 @@ impl From<&ProvenTransaction> for TransactionId { impl From<&TransactionResult> for TransactionId { fn from(tx: &TransactionResult) -> Self { - let input_notes_hash = tx.consumed_notes().commitment(); - let output_notes_hash = tx.created_notes().commitment(); + let input_notes_hash = tx.input_notes().commitment(); + let output_notes_hash = tx.output_notes().commitment(); Self::new( tx.initial_account_hash(), tx.final_account_hash(), diff --git a/objects/src/transaction/tx_result.rs b/objects/src/transaction/tx_result.rs index 377b6501c..1961a77b8 100644 --- a/objects/src/transaction/tx_result.rs +++ b/objects/src/transaction/tx_result.rs @@ -2,10 +2,13 @@ use vm_processor::{AdviceInputs, Program}; use crate::{ accounts::{Account, AccountDelta, AccountId}, - transaction::{CreatedNotes, FinalAccountStub, InputNotes, TransactionWitness}, + transaction::{FinalAccountStub, InputNotes, OutputNotes, TransactionWitness}, Digest, TransactionResultError, }; +// TRANSACTION RESULT +// ================================================================================================ + /// [TransactionResult] represents the result of the execution of the transaction kernel. /// /// [TransactionResult] is a container for the following data: @@ -25,8 +28,8 @@ pub struct TransactionResult { initial_account_hash: Digest, final_account_hash: Digest, account_delta: AccountDelta, - consumed_notes: InputNotes, - created_notes: CreatedNotes, + input_notes: InputNotes, + output_notes: OutputNotes, block_hash: Digest, program: Program, tx_script_root: Option, @@ -42,8 +45,8 @@ impl TransactionResult { initial_account: Account, final_account_stub: FinalAccountStub, account_delta: AccountDelta, - consumed_notes: InputNotes, - created_notes: CreatedNotes, + input_notes: InputNotes, + output_notes: OutputNotes, block_hash: Digest, program: Program, tx_script_root: Option, @@ -54,8 +57,8 @@ impl TransactionResult { initial_account_hash: initial_account.hash(), final_account_hash: final_account_stub.0.hash(), account_delta, - consumed_notes, - created_notes, + input_notes, + output_notes, block_hash, program, tx_script_root, @@ -87,13 +90,13 @@ impl TransactionResult { } /// Returns a reference to the consumed notes. - pub fn consumed_notes(&self) -> &InputNotes { - &self.consumed_notes + pub fn input_notes(&self) -> &InputNotes { + &self.input_notes } /// Returns a reference to the created notes. - pub fn created_notes(&self) -> &CreatedNotes { - &self.created_notes + pub fn output_notes(&self) -> &OutputNotes { + &self.output_notes } /// Returns the block hash the transaction was executed against. @@ -123,7 +126,7 @@ impl TransactionResult { self.account_id, self.initial_account_hash, self.block_hash, - self.consumed_notes.commitment(), + self.input_notes.commitment(), self.tx_script_root, self.program, self.advice_witness, diff --git a/objects/src/transaction/tx_witness.rs b/objects/src/transaction/tx_witness.rs index cf7822e7a..b2630be5f 100644 --- a/objects/src/transaction/tx_witness.rs +++ b/objects/src/transaction/tx_witness.rs @@ -11,7 +11,7 @@ use super::{ /// - initial_account_hash: the hash of the initial state of the account the transaction is being /// executed against. /// - block_hash: the block hash of the latest known block. -/// - consumed_notes_hash: a commitment to the consumed notes of the transaction. +/// - input_notes_hash: a commitment to the consumed notes of the transaction. /// - tx_script_root: an optional transaction script root. /// - program: the transaction [Program] /// - advice_witness: the advice inputs for the transaction @@ -19,7 +19,7 @@ pub struct TransactionWitness { account_id: AccountId, initial_account_hash: Digest, block_hash: Digest, - consumed_notes_hash: Digest, + input_notes_hash: Digest, tx_script_root: Option, program: Program, advice_witness: AdviceInputs, @@ -32,7 +32,7 @@ impl TransactionWitness { account_id: AccountId, initial_account_hash: Digest, block_hash: Digest, - consumed_notes_hash: Digest, + input_notes_hash: Digest, tx_script_root: Option, program: Program, advice_witness: AdviceInputs, @@ -41,7 +41,7 @@ impl TransactionWitness { account_id, initial_account_hash, block_hash, - consumed_notes_hash, + input_notes_hash, tx_script_root, program, advice_witness, @@ -63,9 +63,9 @@ impl TransactionWitness { &self.block_hash } - /// Returns the consumed notes hash. - pub fn consumed_notes_hash(&self) -> &Digest { - &self.consumed_notes_hash + /// Returns the hash of input notes. + pub fn input_notes_hash(&self) -> &Digest { + &self.input_notes_hash } /// Returns a vector of [Nullifier] for all consumed notes in the transaction. @@ -73,17 +73,16 @@ impl TransactionWitness { /// # Errors /// - If the consumed notes data is not found in the advice map. /// - If the consumed notes data is not well formed. - pub fn consumed_notes_info(&self) -> Result, TransactionWitnessError> { - // fetch consumed notes data from the advice map + pub fn input_notes_info(&self) -> Result, TransactionWitnessError> { + // fetch input notes data from the advice map let notes_data = self .advice_witness - .mapped_values(&self.consumed_notes_hash.as_bytes()) + .mapped_values(&self.input_notes_hash.as_bytes()) .ok_or(TransactionWitnessError::ConsumedNoteDataNotFound)?; - // extract the notes from the first fetch and instantiate a vector to hold - // [ConsumedNoteInfo]. + // extract the notes from the first fetch and instantiate a vector to hold nullifiers let num_notes = notes_data[0].as_int(); - let mut consumed_notes_info = Vec::with_capacity(num_notes as usize); + let mut input_notes_info = Vec::with_capacity(num_notes as usize); // iterate over the notes and extract the nullifier and script root let mut note_ptr = 1; @@ -97,7 +96,7 @@ impl TransactionWitness { let (nullifier, num_assets) = extract_note_data(¬es_data[note_ptr..]); // push the [ConsumedNoteInfo] to the vector - consumed_notes_info.push(nullifier.into()); + input_notes_info.push(nullifier.into()); // round up the number of assets to the next multiple of 2 to account for asset padding let num_assets = (num_assets + 1) & !1; @@ -107,9 +106,9 @@ impl TransactionWitness { } debug_assert_eq!( - self.consumed_notes_hash, + self.input_notes_hash, Hasher::hash_elements( - &consumed_notes_info + &input_notes_info .iter() .flat_map(|info| { let mut elements = Word::from(info).to_vec(); @@ -120,7 +119,7 @@ impl TransactionWitness { ) ); - Ok(consumed_notes_info) + Ok(input_notes_info) } /// Returns the transaction script root. @@ -136,7 +135,7 @@ impl TransactionWitness { /// Returns the stack inputs for the transaction. pub fn get_stack_inputs(&self) -> StackInputs { let mut inputs: Vec = Vec::with_capacity(13); - inputs.extend(*self.consumed_notes_hash); + inputs.extend(*self.input_notes_hash); inputs.extend(*self.initial_account_hash); inputs.push(self.account_id.into()); inputs.extend(*self.block_hash); @@ -158,7 +157,7 @@ impl TransactionWitness { self.account_id, self.initial_account_hash, self.block_hash, - self.consumed_notes_hash, + self.input_notes_hash, self.tx_script_root, self.program, self.advice_witness,