From d84f4b53129975b7835d8faba49fc44b48f7a5a6 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 | 4 +- miden-tx/src/prover/mod.rs | 6 +- miden-tx/src/result.rs | 7 +- miden-tx/src/tests.rs | 6 +- miden-tx/tests/faucet_contract_test.rs | 9 +- miden-tx/tests/swap_script_test.rs | 9 +- objects/src/notes/mod.rs | 3 - objects/src/transaction/created_notes.rs | 84 ----------- objects/src/transaction/mod.rs | 4 +- objects/src/transaction/outputs.rs | 171 ++++++++++++++++++++++ objects/src/transaction/transaction_id.rs | 4 +- objects/src/transaction/tx_result.rs | 24 +-- 13 files changed, 212 insertions(+), 126 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..803d95e65 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..29d0d60f3 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}; @@ -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 created_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!( diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index e8fe87baf..9853d25c6 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) = @@ -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( diff --git a/miden-tx/src/result.rs b/miden-tx/src/result.rs index 05c33a0f2..dc866be44 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( @@ -56,7 +55,7 @@ impl TryFromVmResult for CreatedNotes { 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)?; created_notes.push(note_stub); 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..cd4bbda92 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,14 @@ 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().notes()[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 +207,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..8808998d7 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().notes().len(), 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).unwrap(); - let created_note = &transaction_result.created_notes().notes()[0]; + let created_note = &transaction_result.output_notes().notes()[0]; assert_eq!(created_note, &requested_note); } 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/mod.rs b/objects/src/transaction/mod.rs index 5935e9951..2ee11fb9b 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; diff --git a/objects/src/transaction/outputs.rs b/objects/src/transaction/outputs.rs new file mode 100644 index 000000000..cc4c630a6 --- /dev/null +++ b/objects/src/transaction/outputs.rs @@ -0,0 +1,171 @@ +use core::iter::FromIterator; + +use crate::{ + notes::{Note, NoteEnvelope, NoteMetadata, NoteVault}, + utils::collections::Vec, + Digest, Felt, Hasher, NoteError, StarkField, Word, +}; + +// OUTPUT 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 OutputNotes { + notes: Vec, + commitment: Digest, +} + +impl OutputNotes { + // 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) -> &[OutputNote] { + &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: &[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) +} + +impl From for Vec { + fn from(created_notes: OutputNotes) -> Self { + (&created_notes).into() + } +} + +impl From<&OutputNotes> for Vec { + fn from(created_notes: &OutputNotes) -> Self { + created_notes.notes.iter().map(|note| note.into()).collect::>() + } +} + +impl From> for OutputNotes { + fn from(notes: Vec) -> Self { + Self::new(notes.into_iter().map(|note| note.into()).collect()) + } +} + +impl From> for OutputNotes { + fn from(notes: Vec<&Note>) -> Self { + Self::new(notes.iter().map(|note| (*note).into()).collect()) + } +} + +impl FromIterator for OutputNotes { + fn from_iter>(iter: T) -> Self { + Self::new(iter.into_iter().map(|v| v.into()).collect()) + } +} + +// OUTPUT NOTE +// ================================================================================================ + +/// An object that represents the stub of a note. When a note is produced in a transaction it can +/// be the case that only the recipient, vault and metadata are known. In this case, the note +/// stub can be used to represent the note. +#[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 + // -------------------------------------------------------------------------------------------- + /// Creates a new [NoteStub]. + pub fn new( + recipient: Digest, + vault: NoteVault, + metadata: NoteMetadata, + ) -> Result { + if vault.num_assets() as u64 != metadata.num_assets().as_int() { + return Err(NoteError::InconsistentStubNumAssets( + vault.num_assets() as u64, + metadata.num_assets().as_int(), + )); + } + let hash = Hasher::merge(&[recipient, vault.hash()]); + Ok(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()) + .expect("Note vault and metadate weren't consistent") + } +} 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..ac15a89af 100644 --- a/objects/src/transaction/tx_result.rs +++ b/objects/src/transaction/tx_result.rs @@ -2,7 +2,7 @@ use vm_processor::{AdviceInputs, Program}; use crate::{ accounts::{Account, AccountDelta, AccountId}, - transaction::{CreatedNotes, FinalAccountStub, InputNotes, TransactionWitness}, + transaction::{FinalAccountStub, InputNotes, OutputNotes, TransactionWitness}, Digest, TransactionResultError, }; @@ -25,8 +25,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 +42,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 +54,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 +87,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 +123,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,