From 17077e8d467b62f529b5dcf8d5a76dbd82349ffe Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Dec 2023 02:15:24 -0800 Subject: [PATCH 01/21] refactor: consolidate input transaction info in a single module --- miden-lib/src/tests/test_note.rs | 4 +- miden-lib/src/tests/test_prologue.rs | 6 +- miden-tx/src/compiler/mod.rs | 7 +- miden-tx/src/compiler/tests.rs | 7 +- miden-tx/src/executor/mod.rs | 4 +- miden-tx/src/prover/mod.rs | 4 +- miden-tx/src/tests.rs | 7 +- miden-tx/src/verifier/mod.rs | 2 +- miden-tx/tests/common/mod.rs | 8 +- miden-tx/tests/faucet_contract_test.rs | 2 +- mock/src/lib.rs | 10 +- mock/src/mock/chain.rs | 14 +- mock/src/mock/transaction.rs | 8 +- objects/src/errors.rs | 20 +- objects/src/lib.rs | 4 +- objects/src/notes/mod.rs | 53 ----- objects/src/transaction/consumed_notes.rs | 140 ------------ objects/src/transaction/executed_tx.rs | 12 +- objects/src/transaction/inputs.rs | 260 +++++++++++++++++++++- objects/src/transaction/mod.rs | 11 +- objects/src/transaction/prepared_tx.rs | 36 ++- objects/src/transaction/proven_tx.rs | 26 +-- objects/src/transaction/transaction_id.rs | 4 +- objects/src/transaction/tx_result.rs | 8 +- objects/src/transaction/utils.rs | 20 +- 25 files changed, 370 insertions(+), 307 deletions(-) delete mode 100644 objects/src/transaction/consumed_notes.rs diff --git a/miden-lib/src/tests/test_note.rs b/miden-lib/src/tests/test_note.rs index b3bd7ed54..cdc48699e 100644 --- a/miden-lib/src/tests/test_note.rs +++ b/miden-lib/src/tests/test_note.rs @@ -75,7 +75,7 @@ fn test_get_sender() { ) .unwrap(); - let sender = transaction.consumed_notes().notes()[0].note().metadata().sender().into(); + let sender = transaction.input_notes().get_note(0).note().metadata().sender().into(); assert_eq!(process.stack.get(0), sender); } @@ -373,7 +373,7 @@ fn note_setup_stack_assertions( let mut expected_stack = [ZERO; 16]; // replace the top four elements with the tx script root - let mut note_script_root = *inputs.consumed_notes().notes()[0].note().script().hash(); + let mut note_script_root = *inputs.input_notes().get_note(0).note().script().hash(); note_script_root.reverse(); expected_stack[..4].copy_from_slice(¬e_script_root); diff --git a/miden-lib/src/tests/test_prologue.rs b/miden-lib/src/tests/test_prologue.rs index b21dd11cb..4d6273daa 100644 --- a/miden-lib/src/tests/test_prologue.rs +++ b/miden-lib/src/tests/test_prologue.rs @@ -99,7 +99,7 @@ fn global_input_memory_assertions( // The nullifier commitment should be stored at the NULLIFIER_COM_PTR assert_eq!( process.get_mem_value(ContextId::root(), NULLIFIER_COM_PTR).unwrap(), - inputs.consumed_notes_commitment().as_elements() + inputs.input_notes().commitment().as_elements() ); // The initial nonce should be stored at the INIT_NONCE_PTR @@ -259,10 +259,10 @@ fn consumed_notes_memory_assertions( // The number of consumed notes should be stored at the CONSUMED_NOTES_OFFSET assert_eq!( process.get_mem_value(ContextId::root(), CONSUMED_NOTE_SECTION_OFFSET).unwrap()[0], - Felt::new(inputs.consumed_notes().notes().len() as u64) + Felt::new(inputs.input_notes().num_notes() as u64) ); - for (note, note_idx) in inputs.consumed_notes().notes().iter().zip(0u32..) { + for (note, note_idx) in inputs.input_notes().iter().zip(0_u32..) { // The note nullifier should be computer and stored at (CONSUMED_NOTES_OFFSET + 1 + note_idx) assert_eq!( process diff --git a/miden-tx/src/compiler/mod.rs b/miden-tx/src/compiler/mod.rs index 93a119c70..735b988fd 100644 --- a/miden-tx/src/compiler/mod.rs +++ b/miden-tx/src/compiler/mod.rs @@ -1,7 +1,6 @@ use miden_objects::{ assembly::{Assembler, AssemblyContext, ModuleAst, ProgramAst}, - notes::RecordedNote, - transaction::TransactionScript, + transaction::{InputNotes, TransactionScript}, Felt, TransactionScriptError, Word, }; use vm_processor::ProgramInfo; @@ -138,7 +137,7 @@ impl TransactionCompiler { pub fn compile_transaction( &mut self, account_id: AccountId, - notes: &[RecordedNote], + notes: &InputNotes, tx_script: Option<&ProgramAst>, ) -> Result { // Fetch the account interface from the `account_procedures` map. Return an error if the @@ -212,7 +211,7 @@ impl TransactionCompiler { fn compile_notes( &mut self, target_account_interface: &[Digest], - notes: &[RecordedNote], + notes: &InputNotes, assembly_context: &mut AssemblyContext, ) -> Result, TransactionCompilerError> { let mut note_programs = Vec::new(); diff --git a/miden-tx/src/compiler/tests.rs b/miden-tx/src/compiler/tests.rs index 6e6455765..0841675e6 100644 --- a/miden-tx/src/compiler/tests.rs +++ b/miden-tx/src/compiler/tests.rs @@ -1,7 +1,8 @@ use miden_objects::{ accounts::ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, assets::{Asset, FungibleAsset}, - notes::{Note, NoteInclusionProof, RecordedNote}, + notes::{Note, NoteInclusionProof}, + transaction::{InputNote, InputNotes}, Felt, FieldElement, Word, }; @@ -196,9 +197,11 @@ fn test_transaction_compilation_succeeds() { .unwrap(); let notes = notes .into_iter() - .map(|note| RecordedNote::new(note, mock_inclusion_proof.clone())) + .map(|note| InputNote::new(note, mock_inclusion_proof.clone())) .collect::>(); + let notes = InputNotes::new(notes).unwrap(); + let tx_script_src = format!("begin call.{ACCT_PROC_2} end"); let tx_script_ast = ProgramAst::parse(tx_script_src.as_str()).unwrap(); diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index 8596586b3..6c0ee771b 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::{ConsumedNotes, CreatedNotes, FinalAccountStub, TransactionScript}, + transaction::{CreatedNotes, FinalAccountStub, InputNotes, 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: ConsumedNotes, + consumed_notes: InputNotes, block_hash: Digest, program: Program, tx_script_root: Option, diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index e9708665f..e8fe87baf 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -53,14 +53,14 @@ impl TransactionProver { let created_notes = CreatedNotes::try_from_vm_result(&outputs, &stack, &map, &store) .map_err(TransactionProverError::TransactionResultError)?; - let (account, block_header, _chain, consumed_notes, _tx_program, tx_script) = + let (account, block_header, _chain, input_notes, _tx_program, tx_script) = transaction.into_parts(); Ok(ProvenTransaction::new( account.id(), account.hash(), final_account_stub.0.hash(), - consumed_notes.into(), + input_notes.nullifiers().collect(), created_notes.into(), tx_script.map(|tx_script| *tx_script.hash()), block_header.hash(), diff --git a/miden-tx/src/tests.rs b/miden-tx/src/tests.rs index 0347266b3..b0ca91cd4 100644 --- a/miden-tx/src/tests.rs +++ b/miden-tx/src/tests.rs @@ -3,8 +3,7 @@ use miden_objects::{ assembly::{Assembler, ModuleAst, ProgramAst}, assets::{Asset, FungibleAsset}, block::BlockHeader, - notes::RecordedNote, - transaction::{ChainMmr, CreatedNotes, FinalAccountStub}, + transaction::{ChainMmr, CreatedNotes, FinalAccountStub, InputNote, InputNotes}, Felt, Word, }; use miden_prover::ProvingOptions; @@ -378,7 +377,7 @@ struct MockDataStore { pub account: Account, pub block_header: BlockHeader, pub block_chain: ChainMmr, - pub notes: Vec, + pub notes: Vec, } impl MockDataStore { @@ -417,7 +416,7 @@ impl DataStore for MockDataStore { account_seed: None, block_header: self.block_header, block_chain: self.block_chain.clone(), - input_notes: self.notes.clone(), + input_notes: InputNotes::new(self.notes.clone()).unwrap(), }) } diff --git a/miden-tx/src/verifier/mod.rs b/miden-tx/src/verifier/mod.rs index 4a144c5ad..924e9024d 100644 --- a/miden-tx/src/verifier/mod.rs +++ b/miden-tx/src/verifier/mod.rs @@ -82,7 +82,7 @@ impl TransactionVerifier { fn build_stack_inputs(transaction: &ProvenTransaction) -> StackInputs { let mut stack_inputs: Vec = Vec::with_capacity(13); stack_inputs.extend_from_slice( - Self::compute_consumed_notes_hash(transaction.consumed_notes()).as_elements(), + Self::compute_consumed_notes_hash(transaction.input_note_nullifiers()).as_elements(), ); stack_inputs.extend_from_slice(transaction.initial_account_hash().as_elements()); stack_inputs.push(transaction.account_id().into()); diff --git a/miden-tx/tests/common/mod.rs b/miden-tx/tests/common/mod.rs index 2a09973da..2290ea479 100644 --- a/miden-tx/tests/common/mod.rs +++ b/miden-tx/tests/common/mod.rs @@ -4,8 +4,8 @@ use miden_objects::{ assembly::{ModuleAst, ProgramAst}, assets::{Asset, FungibleAsset}, crypto::{dsa::rpo_falcon512::KeyPair, utils::Serializable}, - notes::{Note, NoteOrigin, NoteScript, RecordedNote}, - transaction::{ChainMmr, TransactionInputs}, + notes::{Note, NoteOrigin, NoteScript}, + transaction::{ChainMmr, InputNote, InputNotes, TransactionInputs}, BlockHeader, Felt, Word, }; use miden_tx::{DataStore, DataStoreError}; @@ -27,7 +27,7 @@ pub struct MockDataStore { pub account: Account, pub block_header: BlockHeader, pub block_chain: ChainMmr, - pub notes: Vec, + pub notes: Vec, } impl MockDataStore { @@ -89,7 +89,7 @@ impl DataStore for MockDataStore { account_seed: None, block_header: self.block_header, block_chain: self.block_chain.clone(), - input_notes: self.notes.clone(), + input_notes: InputNotes::new(self.notes.clone()).unwrap(), }) } diff --git a/miden-tx/tests/faucet_contract_test.rs b/miden-tx/tests/faucet_contract_test.rs index a169eb614..31a67d420 100644 --- a/miden-tx/tests/faucet_contract_test.rs +++ b/miden-tx/tests/faucet_contract_test.rs @@ -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().notes()[0].note().hash(), note.hash()); + assert_eq!(transaction_result.consumed_notes().get_note(0).note().hash(), note.hash()); } #[test] diff --git a/mock/src/lib.rs b/mock/src/lib.rs index 00d1ce8f2..4eea167d5 100644 --- a/mock/src/lib.rs +++ b/mock/src/lib.rs @@ -3,8 +3,10 @@ use std::{fs::File, io::Read, path::PathBuf}; use miden_lib::{assembler::assembler, memory}; use miden_objects::{ accounts::Account, - notes::{Note, NoteVault, RecordedNote}, - transaction::{ChainMmr, PreparedTransaction, TransactionInputs, TransactionScript}, + notes::{Note, NoteVault}, + transaction::{ + ChainMmr, InputNote, InputNotes, PreparedTransaction, TransactionInputs, TransactionScript, + }, BlockHeader, Felt, StarkField, }; use vm_processor::{ @@ -92,7 +94,7 @@ pub fn prepare_transaction( account_seed: Option, block_header: BlockHeader, chain: ChainMmr, - notes: Vec, + notes: Vec, tx_script: Option, code: &str, imports: &str, @@ -112,7 +114,7 @@ pub fn prepare_transaction( account_seed, block_header, block_chain: chain, - input_notes: notes, + input_notes: InputNotes::new(notes).unwrap(), }; PreparedTransaction::new(program, tx_script, tx_inputs).unwrap() diff --git a/mock/src/mock/chain.rs b/mock/src/mock/chain.rs index d4f5f857b..056f9c787 100644 --- a/mock/src/mock/chain.rs +++ b/mock/src/mock/chain.rs @@ -4,8 +4,8 @@ use miden_objects::{ accounts::{Account, AccountId, AccountType, SlotItem}, assets::Asset, crypto::merkle::{Mmr, NodeIndex, PartialMmr, SimpleSmt, TieredSmt}, - notes::{Note, NoteInclusionProof, RecordedNote, NOTE_LEAF_DEPTH, NOTE_TREE_DEPTH}, - transaction::ChainMmr, + notes::{Note, NoteInclusionProof, NOTE_LEAF_DEPTH, NOTE_TREE_DEPTH}, + transaction::{ChainMmr, InputNote}, utils::collections::{BTreeMap, Vec}, BlockHeader, Digest, Felt, FieldElement, StarkField, Word, }; @@ -33,7 +33,7 @@ pub struct Objects { fungible_faucets: Vec<(AccountId, FungibleAssetBuilder)>, nonfungible_faucets: Vec<(AccountId, NonFungibleAssetBuilder)>, notes: Vec, - recorded_notes: Vec, + recorded_notes: Vec, nullifiers: Vec, } @@ -94,7 +94,7 @@ impl Objects { /// Given the [BlockHeader] and its notedb's [SimpleSmt], set all the [Note]'s proof. /// /// Update the [Note]'s proof once the [BlockHeader] has been created. - fn finalize_notes(&mut self, header: BlockHeader, notes: &SimpleSmt) -> Vec { + fn finalize_notes(&mut self, header: BlockHeader, notes: &SimpleSmt) -> Vec { self.notes .drain(..) .enumerate() @@ -103,7 +103,7 @@ impl Objects { NodeIndex::new(NOTE_TREE_DEPTH, index as u64).expect("index bigger than 2**20"); let note_path = notes.get_path(auth_index).expect("auth_index outside of SimpleSmt range"); - RecordedNote::new( + InputNote::new( note.clone(), NoteInclusionProof::new( header.block_num(), @@ -600,7 +600,7 @@ where Ok(data) } -pub fn mock_chain_data(consumed_notes: Vec) -> (ChainMmr, Vec) { +pub fn mock_chain_data(consumed_notes: Vec) -> (ChainMmr, Vec) { let mut note_trees = Vec::new(); // TODO: Consider how to better represent note authentication data. @@ -639,7 +639,7 @@ pub fn mock_chain_data(consumed_notes: Vec) -> (ChainMmr, Vec (Account, BlockHeader, ChainMmr, Vec) { +) -> (Account, BlockHeader, ChainMmr, Vec) { // Create assembler and assembler context let assembler = assembler(); @@ -56,7 +56,7 @@ pub fn mock_inputs_with_existing( asset_preservation: AssetPreservationStatus, account: Option, consumed_notes_from: Option>, -) -> (Account, BlockHeader, ChainMmr, Vec, AdviceInputs) { +) -> (Account, BlockHeader, ChainMmr, Vec, AdviceInputs) { // create auxiliary data object let auxiliary_data = AdviceInputs::default(); diff --git a/objects/src/errors.rs b/objects/src/errors.rs index fffcaed2c..adf222b65 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -269,10 +269,28 @@ impl fmt::Display for ChainMmrError { #[cfg(feature = "std")] impl std::error::Error for ChainMmrError {} +// TRANSACTION INPUTS ERROR +// ================================================================================================ + +#[derive(Debug, Clone)] +pub enum TransactionInputsError { + DuplicateInputNote(Digest), + TooManyInputNotes { max: usize, actual: usize }, +} + +impl fmt::Display for TransactionInputsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TransactionInputsError {} + // TRANSACTION SCRIPT ERROR // ================================================================================================ -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum TransactionScriptError { ScriptCompilationError(AssemblyError), } diff --git a/objects/src/lib.rs b/objects/src/lib.rs index b48ef81f4..fadfed368 100644 --- a/objects/src/lib.rs +++ b/objects/src/lib.rs @@ -21,8 +21,8 @@ pub mod transaction; mod errors; pub use errors::{ AccountDeltaError, AccountError, AssetError, ChainMmrError, ExecutedTransactionError, - NoteError, PreparedTransactionError, TransactionResultError, TransactionScriptError, - TransactionWitnessError, + NoteError, PreparedTransactionError, TransactionInputsError, TransactionResultError, + TransactionScriptError, TransactionWitnessError, }; // RE-EXPORTS // ================================================================================================ diff --git a/objects/src/notes/mod.rs b/objects/src/notes/mod.rs index 73ebd96bd..4f63da13e 100644 --- a/objects/src/notes/mod.rs +++ b/objects/src/notes/mod.rs @@ -185,43 +185,6 @@ impl Note { } } -// RECORDED NOTE -// ================================================================================================ - -/// Represents a note which has been recorded in the Miden notes database. -/// -/// This struct is composed: -/// - A note which has been recorded. -/// - The inclusion proof of the note. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct RecordedNote { - note: Note, - proof: NoteInclusionProof, -} - -impl RecordedNote { - /// Returns a new instance of a [RecordedNote] with the specified note and origin. - pub fn new(note: Note, proof: NoteInclusionProof) -> Self { - Self { note, proof } - } - - /// Returns a reference to the note which was recorded. - pub fn note(&self) -> &Note { - &self.note - } - - /// Returns a reference to the inclusion proof of the recorded note. - pub fn proof(&self) -> &NoteInclusionProof { - &self.proof - } - - /// Returns a reference to the origin of the recorded note. - pub fn origin(&self) -> &NoteOrigin { - self.proof.origin() - } -} - // SERIALIZATION // ================================================================================================ @@ -253,22 +216,6 @@ impl Deserializable for Note { } } -impl Serializable for RecordedNote { - fn write_into(&self, target: &mut W) { - self.note.write_into(target); - self.proof.write_into(target); - } -} - -impl Deserializable for RecordedNote { - fn read_from(source: &mut R) -> Result { - let note = Note::read_from(source)?; - let proof = NoteInclusionProof::read_from(source)?; - - Ok(Self { note, proof }) - } -} - #[cfg(feature = "serde")] mod serialization { use super::NoteScript; diff --git a/objects/src/transaction/consumed_notes.rs b/objects/src/transaction/consumed_notes.rs deleted file mode 100644 index 2c17015f4..000000000 --- a/objects/src/transaction/consumed_notes.rs +++ /dev/null @@ -1,140 +0,0 @@ -use miden_crypto::utils::{ByteReader, ByteWriter, Deserializable, Serializable}; -use vm_processor::DeserializationError; - -use super::{ - utils::generate_consumed_notes_commitment, AdviceInputsBuilder, Digest, Felt, Nullifier, - RecordedNote, ToAdviceInputs, Vec, Word, -}; - -// CONSUMED NOTES -// ================================================================================================ - -/// An object that holds a list of notes that were consumed by a transaction. -/// -/// This objects primary use case is to enable all consumed notes to be populated into the advice -/// provider at once via the [ToAdviceInputs] trait. -#[derive(Debug, Clone)] -pub struct ConsumedNotes { - notes: Vec, - commitment: Digest, -} - -impl ConsumedNotes { - /// Creates a new [ConsumedNotes] object. - pub fn new(notes: Vec) -> Self { - assert!(notes.len() <= u16::MAX.into()); - let commitment = generate_consumed_notes_commitment(¬es); - Self { notes, commitment } - } - - /// Returns the consumed notes. - pub fn notes(&self) -> &[RecordedNote] { - &self.notes - } - - /// Returns a commitment to the consumed notes. - pub fn commitment(&self) -> Digest { - self.commitment - } -} - -impl ToAdviceInputs for ConsumedNotes { - /// Populates the advice inputs for all consumed notes. - /// - /// For each note the authentication path is populated into the Merkle store, the note inputs - /// and vault assets are populated in the advice map. A combined note data vector is also - /// constructed that holds core data for all notes. This combined vector is added to the advice - /// map against the consumed notes commitment. For each note the following data items are added - /// to the vector: - /// out[0..4] = serial num - /// out[4..8] = script root - /// out[8..12] = input root - /// out[12..16] = vault_hash - /// out[16..20] = metadata - /// out[20..24] = asset_1 - /// out[24..28] = asset_2 - /// ... - /// out[20 + num_assets * 4..] = Word::default() (this is conditional padding only applied - /// if the number of assets is odd) - /// out[-10] = origin.block_number - /// out[-9..-5] = origin.SUB_HASH - /// out[-5..-1] = origin.NOTE_ROOT - /// out[-1] = origin.node_index - fn to_advice_inputs(&self, target: &mut T) { - let mut note_data: Vec = Vec::new(); - - note_data.push(Felt::from(self.notes.len() as u64)); - - for recorded_note in &self.notes { - let note = recorded_note.note(); - let proof = recorded_note.proof(); - - note_data.extend(note.serial_num()); - note_data.extend(*note.script().hash()); - note_data.extend(*note.inputs().hash()); - note_data.extend(*note.vault().hash()); - note_data.extend(Word::from(note.metadata())); - - note_data.extend(note.vault().to_padded_assets()); - target.insert_into_map(note.vault().hash().into(), note.vault().to_padded_assets()); - - note_data.push(proof.origin().block_num.into()); - note_data.extend(*proof.sub_hash()); - note_data.extend(*proof.note_root()); - note_data.push(Felt::from(proof.origin().node_index.value())); - target.add_merkle_nodes( - proof - .note_path() - .inner_nodes(proof.origin().node_index.value(), note.authentication_hash()) - .unwrap(), - ); - - target.insert_into_map(note.inputs().hash().into(), note.inputs().inputs().to_vec()); - } - - target.insert_into_map(*self.commitment, note_data); - } -} - -impl From for Vec { - fn from(consumed_notes: ConsumedNotes) -> Self { - consumed_notes - .notes - .into_iter() - .map(|note| note.note().nullifier()) - .collect::>() - } -} - -impl From> for ConsumedNotes { - fn from(recorded_notes: Vec) -> Self { - Self::new(recorded_notes) - } -} - -impl FromIterator for ConsumedNotes { - fn from_iter>(iter: T) -> Self { - Self::new(iter.into_iter().collect()) - } -} - -// SERIALIZATION -// ================================================================================================ -// - -impl Serializable for ConsumedNotes { - fn write_into(&self, target: &mut W) { - assert!(self.notes.len() <= u16::MAX.into()); - target.write_u16(self.notes.len() as u16); - self.notes.write_into(target); - } -} - -impl Deserializable for ConsumedNotes { - fn read_from(source: &mut R) -> Result { - let count = source.read_u16()?; - let notes = RecordedNote::read_batch_from(source, count.into())?; - - Ok(Self::new(notes)) - } -} diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index 80fe005f9..c438e894b 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -4,8 +4,8 @@ use super::TransactionScript; use crate::{ accounts::validate_account_seed, transaction::{ - utils, Account, AdviceInputs, BlockHeader, ChainMmr, ConsumedNotes, Digest, Note, - RecordedNote, StackInputs, Vec, Word, + utils, Account, AdviceInputs, BlockHeader, ChainMmr, Digest, InputNote, InputNotes, Note, + StackInputs, Vec, Word, }, ExecutedTransactionError, }; @@ -15,7 +15,7 @@ pub struct ExecutedTransaction { initial_account: Account, initial_account_seed: Option, final_account: Account, - consumed_notes: ConsumedNotes, + consumed_notes: InputNotes, created_notes: Vec, tx_script: Option, block_header: BlockHeader, @@ -29,7 +29,7 @@ impl ExecutedTransaction { initial_account: Account, initial_account_seed: Option, final_account: Account, - consumed_notes: Vec, + consumed_notes: Vec, created_notes: Vec, tx_script: Option, block_header: BlockHeader, @@ -40,7 +40,7 @@ impl ExecutedTransaction { initial_account, initial_account_seed, final_account, - consumed_notes: ConsumedNotes::new(consumed_notes), + consumed_notes: InputNotes::new(consumed_notes).unwrap(), created_notes, tx_script, block_header, @@ -59,7 +59,7 @@ impl ExecutedTransaction { } /// Returns the consumed notes. - pub fn consumed_notes(&self) -> &ConsumedNotes { + pub fn consumed_notes(&self) -> &InputNotes { &self.consumed_notes } diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index f9a3e83e1..4c8148687 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -1,4 +1,18 @@ -use super::{Account, BlockHeader, ChainMmr, RecordedNote, Vec, Word}; +use core::cell::OnceCell; + +use super::{ + Account, AdviceInputsBuilder, BlockHeader, ChainMmr, Digest, Felt, Hasher, Note, Nullifier, + ToAdviceInputs, Vec, Word, MAX_NOTES_PER_TRANSACTION, +}; +use crate::{ + notes::{NoteInclusionProof, NoteOrigin}, + utils::{ + collections::{self, BTreeSet}, + serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, + string::ToString, + }, + TransactionInputsError, +}; // TRANSACTION INPUTS // ================================================================================================ @@ -9,5 +23,247 @@ pub struct TransactionInputs { pub account_seed: Option, pub block_header: BlockHeader, pub block_chain: ChainMmr, - pub input_notes: Vec, + pub input_notes: InputNotes, +} + +// INPUT NOTES +// ================================================================================================ + +/// Contains a list of input notes for a transaction. +/// +/// The list can be empty if the transaction does not consume any notes. +#[derive(Debug, Clone)] +pub struct InputNotes { + notes: Vec, + commitment: OnceCell, +} + +impl InputNotes { + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + /// Returns a 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 { + return Err(TransactionInputsError::TooManyInputNotes { + max: MAX_NOTES_PER_TRANSACTION, + actual: notes.len(), + }); + } + + let mut seen_notes = BTreeSet::new(); + for note in notes.iter() { + if !seen_notes.insert(note.note().hash()) { + return Err(TransactionInputsError::DuplicateInputNote(note.note().hash())); + } + } + + Ok(Self { notes, commitment: OnceCell::new() }) + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns a commitment to these input notes. + pub fn commitment(&self) -> Digest { + *self.commitment.get_or_init(|| build_input_notes_commitment(self.nullifiers())) + } + + /// Returns total number of input notes. + pub fn num_notes(&self) -> usize { + self.notes.len() + } + + /// Returns true if this [InputNotes] does not contain any notes. + pub fn is_empty(&self) -> bool { + self.notes.is_empty() + } + + /// Returns a reference to the [InputNote] located at the specified index. + pub fn get_note(&self, idx: usize) -> &InputNote { + &self.notes[idx] + } + + // ITERATORS + // -------------------------------------------------------------------------------------------- + + /// Returns an iterator over notes in this [InputNotes]. + pub fn iter(&self) -> impl Iterator { + self.notes.iter() + } + + /// Returns an iterator over nullifiers of all notes in this [InputNotes]. + pub fn nullifiers(&self) -> impl Iterator + '_ { + self.notes.iter().map(|note| note.note().nullifier()) + } +} + +impl IntoIterator for InputNotes { + type Item = InputNote; + type IntoIter = collections::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.notes.into_iter() + } +} + +impl PartialEq for InputNotes { + fn eq(&self, other: &Self) -> bool { + self.notes == other.notes + } +} + +impl Eq for InputNotes {} + +// ADVICE INPUTS +// -------------------------------------------------------------------------------------------- + +impl ToAdviceInputs for InputNotes { + /// Populates the advice inputs for all consumed notes. + /// + /// For each note the authentication path is populated into the Merkle store, the note inputs + /// and vault assets are populated in the advice map. A combined note data vector is also + /// constructed that holds core data for all notes. This combined vector is added to the advice + /// map against the consumed notes commitment. For each note the following data items are added + /// to the vector: + /// out[0..4] = serial num + /// out[4..8] = script root + /// out[8..12] = input root + /// out[12..16] = vault_hash + /// out[16..20] = metadata + /// out[20..24] = asset_1 + /// out[24..28] = asset_2 + /// ... + /// out[20 + num_assets * 4..] = Word::default() (this is conditional padding only applied + /// if the number of assets is odd) + /// out[-10] = origin.block_number + /// out[-9..-5] = origin.SUB_HASH + /// out[-5..-1] = origin.NOTE_ROOT + /// out[-1] = origin.node_index + fn to_advice_inputs(&self, target: &mut T) { + let mut note_data: Vec = Vec::new(); + + note_data.push(Felt::from(self.notes.len() as u64)); + + for recorded_note in &self.notes { + let note = recorded_note.note(); + let proof = recorded_note.proof(); + + note_data.extend(note.serial_num()); + note_data.extend(*note.script().hash()); + note_data.extend(*note.inputs().hash()); + note_data.extend(*note.vault().hash()); + note_data.extend(Word::from(note.metadata())); + + note_data.extend(note.vault().to_padded_assets()); + target.insert_into_map(note.vault().hash().into(), note.vault().to_padded_assets()); + + note_data.push(proof.origin().block_num.into()); + note_data.extend(*proof.sub_hash()); + note_data.extend(*proof.note_root()); + note_data.push(Felt::from(proof.origin().node_index.value())); + target.add_merkle_nodes( + proof + .note_path() + .inner_nodes(proof.origin().node_index.value(), note.authentication_hash()) + .unwrap(), + ); + + target.insert_into_map(note.inputs().hash().into(), note.inputs().inputs().to_vec()); + } + + target.insert_into_map(self.commitment().into(), note_data); + } +} + +// SERIALIZATION +// ------------------------------------------------------------------------------------------------ + +impl Serializable for InputNotes { + fn write_into(&self, target: &mut W) { + // assert is OK here because we enforce max number of notes in the constructor + assert!(self.notes.len() <= u16::MAX.into()); + target.write_u16(self.notes.len() as u16); + self.notes.write_into(target); + } +} + +impl Deserializable for InputNotes { + fn read_from(source: &mut R) -> Result { + let num_notes = source.read_u16()?; + let notes = InputNote::read_batch_from(source, num_notes.into())?; + Self::new(notes).map_err(|err| DeserializationError::InvalidValue(err.to_string())) + } +} + +// HELPER FUNCTIONS +// ------------------------------------------------------------------------------------------------ + +/// Returns the commitment to the input notes represented by the specified nullifiers. +/// +/// This is a sequential hash of all (nullifier, ZERO) pairs for the notes consumed in the +/// transaction. +pub fn build_input_notes_commitment>(nullifiers: I) -> Digest { + let mut elements: Vec = Vec::new(); + for nullifier in nullifiers { + elements.extend_from_slice(nullifier.as_elements()); + elements.extend_from_slice(&Word::default()); + } + Hasher::hash_elements(&elements) +} + +// RECORDED NOTE +// ================================================================================================ + +/// An input note for a transaction. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct InputNote { + note: Note, + proof: NoteInclusionProof, +} + +impl InputNote { + /// Returns a new instance of a [RecordedNote] with the specified note and origin. + pub fn new(note: Note, proof: NoteInclusionProof) -> Self { + Self { note, proof } + } + + /// Returns a reference to the note which was recorded. + pub fn note(&self) -> &Note { + &self.note + } + + /// Returns a reference to the inclusion proof of the recorded note. + pub fn proof(&self) -> &NoteInclusionProof { + &self.proof + } + + /// Returns a reference to the origin of the recorded note. + pub fn origin(&self) -> &NoteOrigin { + self.proof.origin() + } +} + +// SERIALIZATION +// ------------------------------------------------------------------------------------------------ + +impl Serializable for InputNote { + fn write_into(&self, target: &mut W) { + self.note.write_into(target); + self.proof.write_into(target); + } +} + +impl Deserializable for InputNote { + fn read_from(source: &mut R) -> Result { + let note = Note::read_from(source)?; + let proof = NoteInclusionProof::read_from(source)?; + + Ok(Self { note, proof }) + } } diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index 0efb352e9..5935e9951 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -2,7 +2,7 @@ use vm_core::{Program, StackInputs, StackOutputs}; use super::{ accounts::{Account, AccountId}, - notes::{Note, NoteEnvelope, Nullifier, RecordedNote}, + notes::{Note, NoteEnvelope, Nullifier}, utils::collections::Vec, AdviceInputs, AdviceInputsBuilder, BlockHeader, Digest, Felt, Hasher, PreparedTransactionError, StarkField, ToAdviceInputs, TransactionWitnessError, Word, WORD_SIZE, ZERO, @@ -10,7 +10,6 @@ use super::{ mod account_stub; mod chain_mmr; -mod consumed_notes; mod created_notes; mod event; mod executed_tx; @@ -26,11 +25,10 @@ mod utils; pub use account_stub::FinalAccountStub; pub use chain_mmr::ChainMmr; -pub use consumed_notes::ConsumedNotes; pub use created_notes::CreatedNotes; pub use event::Event; pub use executed_tx::ExecutedTransaction; -pub use inputs::TransactionInputs; +pub use inputs::{InputNote, InputNotes, TransactionInputs}; pub use prepared_tx::PreparedTransaction; pub use proven_tx::ProvenTransaction; pub use script::TransactionScript; @@ -40,3 +38,8 @@ pub use tx_witness::TransactionWitness; #[cfg(feature = "testing")] pub mod utils; + +// CONSTANTS +// ================================================================================================ + +const MAX_NOTES_PER_TRANSACTION: usize = 1024; diff --git a/objects/src/transaction/prepared_tx.rs b/objects/src/transaction/prepared_tx.rs index 9cb08048f..146f5cac4 100644 --- a/objects/src/transaction/prepared_tx.rs +++ b/objects/src/transaction/prepared_tx.rs @@ -1,5 +1,5 @@ use super::{ - utils, Account, AdviceInputs, BlockHeader, ChainMmr, ConsumedNotes, Digest, + utils, Account, AdviceInputs, BlockHeader, ChainMmr, Digest, InputNotes, PreparedTransactionError, Program, StackInputs, TransactionInputs, TransactionScript, Word, }; use crate::accounts::validate_account_seed; @@ -14,7 +14,7 @@ use crate::accounts::validate_account_seed; /// - account_seed: An optional account seed used to create a new account. /// - block_header: The header of the latest known block. /// - block_chain: The chain MMR associated with the latest known block. -/// - consumed_notes: A vector of consumed notes. +/// - input_notes: A vector of notes consumed by the transaction. /// - tx_script: An optional transaction script. /// - tx_program: The transaction program. #[derive(Debug)] @@ -23,7 +23,7 @@ pub struct PreparedTransaction { account_seed: Option, block_header: BlockHeader, block_chain: ChainMmr, - consumed_notes: ConsumedNotes, + input_notes: InputNotes, tx_script: Option, tx_program: Program, } @@ -44,7 +44,7 @@ impl PreparedTransaction { account_seed: tx_inputs.account_seed, block_header: tx_inputs.block_header, block_chain: tx_inputs.block_chain, - consumed_notes: ConsumedNotes::new(tx_inputs.input_notes), + input_notes: tx_inputs.input_notes, tx_script, tx_program: program, }) @@ -68,9 +68,9 @@ impl PreparedTransaction { &self.block_chain } - /// Returns the consumed notes. - pub fn consumed_notes(&self) -> &ConsumedNotes { - &self.consumed_notes + /// Returns the input notes. + pub fn input_notes(&self) -> &InputNotes { + &self.input_notes } /// Return a reference the transaction script. @@ -93,7 +93,7 @@ impl PreparedTransaction { utils::generate_stack_inputs( &self.account.id(), initial_acct_hash, - self.consumed_notes.commitment(), + self.input_notes.commitment(), &self.block_header, ) } @@ -105,18 +105,14 @@ impl PreparedTransaction { self.account_seed, &self.block_header, &self.block_chain, - &self.consumed_notes, + &self.input_notes, &self.tx_script, ) } - /// Returns the consumed notes commitment. - pub fn consumed_notes_commitment(&self) -> Digest { - self.consumed_notes.commitment() - } - // HELPERS // -------------------------------------------------------------------------------------------- + /// Validates that a valid account seed has been provided if the account the transaction is /// being executed against is new. fn validate_new_account_seed( @@ -133,22 +129,16 @@ impl PreparedTransaction { // CONSUMERS // -------------------------------------------------------------------------------------------- + /// Consumes the prepared transaction and returns its parts. pub fn into_parts( self, - ) -> ( - Account, - BlockHeader, - ChainMmr, - ConsumedNotes, - Program, - Option, - ) { + ) -> (Account, BlockHeader, ChainMmr, InputNotes, Program, Option) { ( self.account, self.block_header, self.block_chain, - self.consumed_notes, + self.input_notes, self.tx_program, self.tx_script, ) diff --git a/objects/src/transaction/proven_tx.rs b/objects/src/transaction/proven_tx.rs index 531aa554e..4f4a942f7 100644 --- a/objects/src/transaction/proven_tx.rs +++ b/objects/src/transaction/proven_tx.rs @@ -10,7 +10,7 @@ use super::{AccountId, Digest, NoteEnvelope, Nullifier, TransactionId, Vec}; /// - account_id: the account that the transaction was executed against. /// - initial_account_hash: the hash of the account before the transaction was executed. /// - final_account_hash: the hash of the account after the transaction was executed. -/// - consumed_notes: a list of consumed notes defined by their nullifiers. +/// - input_notes: a list of nullifier for all notes consumed by the transaction. /// - created_notes: a list of created notes. /// - tx_script_root: the script root of the transaction. /// - block_ref: the block hash of the last known block at the time the transaction was executed. @@ -20,7 +20,7 @@ pub struct ProvenTransaction { account_id: AccountId, initial_account_hash: Digest, final_account_hash: Digest, - consumed_notes: Vec, + input_note_nullifiers: Vec, created_notes: Vec, tx_script_root: Option, block_ref: Digest, @@ -34,7 +34,7 @@ impl ProvenTransaction { account_id: AccountId, initial_account_hash: Digest, final_account_hash: Digest, - consumed_notes: Vec, + input_note_nullifiers: Vec, created_notes: Vec, tx_script_root: Option, block_ref: Digest, @@ -44,7 +44,7 @@ impl ProvenTransaction { account_id, initial_account_hash, final_account_hash, - consumed_notes, + input_note_nullifiers, created_notes, tx_script_root, block_ref, @@ -76,8 +76,8 @@ impl ProvenTransaction { } /// Returns the nullifiers of consumed notes. - pub fn consumed_notes(&self) -> &[Nullifier] { - &self.consumed_notes + pub fn input_note_nullifiers(&self) -> &[Nullifier] { + &self.input_note_nullifiers } /// Returns the created notes. @@ -109,8 +109,8 @@ impl Serializable for ProvenTransaction { self.account_id.write_into(target); self.initial_account_hash.write_into(target); self.final_account_hash.write_into(target); - target.write_u64(self.consumed_notes.len() as u64); - self.consumed_notes.write_into(target); + target.write_u64(self.input_note_nullifiers.len() as u64); + self.input_note_nullifiers.write_into(target); target.write_u64(self.created_notes.len() as u64); self.created_notes.write_into(target); self.tx_script_root.write_into(target); @@ -125,11 +125,11 @@ impl Deserializable for ProvenTransaction { let initial_account_hash = Digest::read_from(source)?; let final_account_hash = Digest::read_from(source)?; - let count = source.read_u64()?; - let consumed_notes = Nullifier::read_batch_from(source, count as usize)?; + let num_input_notes = source.read_u64()?; + let input_notes = Nullifier::read_batch_from(source, num_input_notes as usize)?; - let count = source.read_u64()?; - let created_notes = NoteEnvelope::read_batch_from(source, count as usize)?; + let num_output_notes = source.read_u64()?; + let created_notes = NoteEnvelope::read_batch_from(source, num_output_notes as usize)?; let tx_script_root = Deserializable::read_from(source)?; @@ -140,7 +140,7 @@ impl Deserializable for ProvenTransaction { account_id, initial_account_hash, final_account_hash, - consumed_notes, + input_note_nullifiers: input_notes, created_notes, tx_script_root, block_ref, diff --git a/objects/src/transaction/transaction_id.rs b/objects/src/transaction/transaction_id.rs index f0c056e93..b314641fb 100644 --- a/objects/src/transaction/transaction_id.rs +++ b/objects/src/transaction/transaction_id.rs @@ -54,8 +54,8 @@ impl From<&ProvenTransaction> for TransactionId { fn from(tx: &ProvenTransaction) -> Self { // TODO: move input/output note hash computations into a more central location let input_notes_hash = { - let mut elements: Vec = Vec::with_capacity(tx.consumed_notes().len() * 8); - for nullifier in tx.consumed_notes().iter() { + let mut elements: Vec = Vec::with_capacity(tx.input_note_nullifiers().len() * 8); + for nullifier in tx.input_note_nullifiers().iter() { elements.extend_from_slice(nullifier.as_elements()); elements.extend_from_slice(&Word::default()); } diff --git a/objects/src/transaction/tx_result.rs b/objects/src/transaction/tx_result.rs index ea37074d5..377b6501c 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::{ConsumedNotes, CreatedNotes, FinalAccountStub, TransactionWitness}, + transaction::{CreatedNotes, FinalAccountStub, InputNotes, TransactionWitness}, Digest, TransactionResultError, }; @@ -25,7 +25,7 @@ pub struct TransactionResult { initial_account_hash: Digest, final_account_hash: Digest, account_delta: AccountDelta, - consumed_notes: ConsumedNotes, + consumed_notes: InputNotes, created_notes: CreatedNotes, block_hash: Digest, program: Program, @@ -42,7 +42,7 @@ impl TransactionResult { initial_account: Account, final_account_stub: FinalAccountStub, account_delta: AccountDelta, - consumed_notes: ConsumedNotes, + consumed_notes: InputNotes, created_notes: CreatedNotes, block_hash: Digest, program: Program, @@ -87,7 +87,7 @@ impl TransactionResult { } /// Returns a reference to the consumed notes. - pub fn consumed_notes(&self) -> &ConsumedNotes { + pub fn consumed_notes(&self) -> &InputNotes { &self.consumed_notes } diff --git a/objects/src/transaction/utils.rs b/objects/src/transaction/utils.rs index 235a4c550..0efd2292d 100644 --- a/objects/src/transaction/utils.rs +++ b/objects/src/transaction/utils.rs @@ -1,9 +1,8 @@ use vm_core::utils::IntoBytes; use super::{ - Account, AccountId, AdviceInputs, BlockHeader, ChainMmr, ConsumedNotes, Digest, Felt, Hasher, - Note, RecordedNote, StackInputs, StackOutputs, ToAdviceInputs, TransactionScript, Vec, Word, - ZERO, + Account, AccountId, AdviceInputs, BlockHeader, ChainMmr, Digest, Felt, Hasher, InputNotes, + Note, StackInputs, StackOutputs, ToAdviceInputs, TransactionScript, Vec, Word, ZERO, }; /// Returns the advice inputs required when executing a transaction. @@ -51,7 +50,7 @@ pub fn generate_advice_provider_inputs( account_id_seed: Option, block_header: &BlockHeader, block_chain: &ChainMmr, - notes: &ConsumedNotes, + notes: &InputNotes, tx_script: &Option, ) -> AdviceInputs { let mut advice_inputs = AdviceInputs::default(); @@ -88,19 +87,6 @@ pub fn generate_advice_provider_inputs( advice_inputs } -/// Returns the consumed notes commitment. -/// -/// This is a sequential hash of all (nullifier, ZERO) pairs for the notes consumed in the -/// transaction. -pub fn generate_consumed_notes_commitment(recorded_notes: &[RecordedNote]) -> Digest { - let mut elements: Vec = Vec::with_capacity(recorded_notes.len() * 8); - for recorded_note in recorded_notes.iter() { - elements.extend_from_slice(recorded_note.note().nullifier().as_elements()); - elements.extend_from_slice(&Word::default()); - } - Hasher::hash_elements(&elements) -} - /// Returns the stack inputs required when executing a transaction. /// This includes the consumed notes commitment, the account hash, the account id, and the block /// reference. From 0a29e02798adf87be4419b9ab10ba0e12aab7ca1 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Dec 2023 02:28:08 -0800 Subject: [PATCH 02/21] 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, From 61ad73775cb6e26e7aced3fcca966b40e0ec77fc Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Dec 2023 10:09:17 -0800 Subject: [PATCH 03/21] refactor: simplify PreparedTransaction --- miden-lib/src/tests/test_account.rs | 12 +-- miden-lib/src/tests/test_asset.rs | 16 +-- miden-lib/src/tests/test_asset_vault.rs | 24 ++--- miden-lib/src/tests/test_faucet.rs | 129 +++++++----------------- miden-lib/src/tests/test_note.rs | 12 +-- miden-lib/src/tests/test_prologue.rs | 18 ++-- miden-lib/src/tests/test_tx.rs | 4 +- miden-tx/src/executor/mod.rs | 2 +- miden-tx/src/prover/mod.rs | 2 +- objects/src/transaction/inputs.rs | 1 + objects/src/transaction/prepared_tx.rs | 112 +++++++++----------- 11 files changed, 130 insertions(+), 202 deletions(-) diff --git a/miden-lib/src/tests/test_account.rs b/miden-lib/src/tests/test_account.rs index d52e831f5..2b0c58b80 100644 --- a/miden-lib/src/tests/test_account.rs +++ b/miden-lib/src/tests/test_account.rs @@ -43,7 +43,7 @@ pub fn test_set_code_is_not_immediate() { prepare_transaction(account, None, block_header, chain, notes, None, code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -261,7 +261,7 @@ fn test_get_item() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let _process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -321,7 +321,7 @@ fn test_set_item() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let _process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -376,7 +376,7 @@ fn test_get_map_item() { ); let _process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -413,7 +413,7 @@ fn test_get_vault_commitment() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let _process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -461,7 +461,7 @@ fn test_authenticate_procedure() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ); diff --git a/miden-lib/src/tests/test_asset.rs b/miden-lib/src/tests/test_asset.rs index 946f3a924..9e349d7c1 100644 --- a/miden-lib/src/tests/test_asset.rs +++ b/miden-lib/src/tests/test_asset.rs @@ -48,11 +48,8 @@ fn test_create_fungible_asset_succeeds() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let _process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); } #[test] @@ -93,10 +90,7 @@ fn test_create_non_fungible_asset_succeeds() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ) - .unwrap(); + let _process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider) + .unwrap(); } diff --git a/miden-lib/src/tests/test_asset_vault.rs b/miden-lib/src/tests/test_asset_vault.rs index a156edc99..4c30892d8 100644 --- a/miden-lib/src/tests/test_asset_vault.rs +++ b/miden-lib/src/tests/test_asset_vault.rs @@ -40,7 +40,7 @@ fn test_get_balance() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -74,7 +74,7 @@ fn test_get_balance_non_fungible_fails() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ); @@ -106,7 +106,7 @@ fn test_has_non_fungible_asset() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - inputs.tx_program().clone(), + inputs.program().clone(), inputs.stack_inputs(), MemAdviceProvider::from(inputs.advice_provider_inputs()), ) @@ -144,7 +144,7 @@ fn test_add_fungible_asset_success() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -190,7 +190,7 @@ fn test_add_non_fungible_asset_fail_overflow() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ); @@ -231,7 +231,7 @@ fn test_add_non_fungible_asset_success() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -278,7 +278,7 @@ fn test_add_non_fungible_asset_fail_duplicate() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ); @@ -316,7 +316,7 @@ fn test_remove_fungible_asset_success_no_balance_remaining() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -361,7 +361,7 @@ fn test_remove_fungible_asset_fail_remove_too_much() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ); @@ -397,7 +397,7 @@ fn test_remove_fungible_asset_success_balance_remaining() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -444,7 +444,7 @@ fn test_remove_non_fungible_asset_fail_doesnt_exist() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ); @@ -483,7 +483,7 @@ fn test_remove_non_fungible_asset_success() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) diff --git a/miden-lib/src/tests/test_faucet.rs b/miden-lib/src/tests/test_faucet.rs index d062265b4..e43aca4e6 100644 --- a/miden-lib/src/tests/test_faucet.rs +++ b/miden-lib/src/tests/test_faucet.rs @@ -67,12 +67,9 @@ fn test_mint_fungible_asset_succeeds() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ) - .unwrap(); + let _process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider) + .unwrap(); } #[test] @@ -98,11 +95,8 @@ fn test_mint_fungible_asset_fails_not_faucet_account() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -135,11 +129,8 @@ fn test_mint_fungible_asset_inconsistent_faucet_id() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -173,11 +164,8 @@ fn test_mint_fungible_asset_fails_saturate_max_amount() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -240,12 +228,9 @@ fn test_mint_non_fungible_asset_succeeds() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ) - .unwrap(); + let _process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider) + .unwrap(); } #[test] @@ -273,11 +258,8 @@ fn test_mint_non_fungible_asset_fails_not_faucet_account() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -306,11 +288,8 @@ fn test_mint_non_fungible_asset_fails_inconsistent_faucet_id() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -345,11 +324,8 @@ fn test_mint_non_fungible_asset_fails_asset_already_exists() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -406,12 +382,9 @@ fn test_burn_fungible_asset_succeeds() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ) - .unwrap(); + let _process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider) + .unwrap(); } #[test] @@ -437,11 +410,8 @@ fn test_burn_fungible_asset_fails_not_faucet_account() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -474,11 +444,8 @@ fn test_burn_fungible_asset_inconsistent_faucet_id() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -512,11 +479,8 @@ fn test_burn_fungible_asset_insufficient_input_amount() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -579,12 +543,9 @@ fn test_burn_non_fungible_asset_succeeds() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ) - .unwrap(); + let _process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider) + .unwrap(); } #[test] @@ -623,11 +584,8 @@ fn test_burn_non_fungible_asset_fails_does_not_exist() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -663,11 +621,8 @@ fn test_burn_non_fungible_asset_fails_not_faucet_account() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -707,11 +662,8 @@ fn test_burn_non_fungible_asset_fails_inconsistent_faucet_id() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ); + let process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -752,10 +704,7 @@ fn test_get_total_issuance_succeeds() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = run_tx( - transaction.tx_program().clone(), - transaction.stack_inputs(), - &mut advice_provider, - ) - .unwrap(); + let _process = + run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider) + .unwrap(); } diff --git a/miden-lib/src/tests/test_note.rs b/miden-lib/src/tests/test_note.rs index cdc48699e..702a1d6d2 100644 --- a/miden-lib/src/tests/test_note.rs +++ b/miden-lib/src/tests/test_note.rs @@ -39,7 +39,7 @@ fn test_get_sender_no_sender() { prepare_transaction(account, None, block_header, chain, notes, None, code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ); @@ -69,7 +69,7 @@ fn test_get_sender() { prepare_transaction(account, None, block_header, chain, notes, None, code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -128,7 +128,7 @@ fn test_get_vault_data() { // run to ensure success let _process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -249,7 +249,7 @@ fn test_get_assets() { ); let _process = run_tx( - inputs.tx_program().clone(), + inputs.program().clone(), inputs.stack_inputs(), MemAdviceProvider::from(inputs.advice_provider_inputs()), ) @@ -331,7 +331,7 @@ fn test_get_inputs() { ); let _process = run_tx( - inputs.tx_program().clone(), + inputs.program().clone(), inputs.stack_inputs(), MemAdviceProvider::from(inputs.advice_provider_inputs()), ) @@ -357,7 +357,7 @@ fn test_note_setup() { prepare_transaction(account, None, block_header, chain, notes, None, code, "", None); let process = run_tx( - inputs.tx_program().clone(), + inputs.program().clone(), inputs.stack_inputs(), MemAdviceProvider::from(inputs.advice_provider_inputs()), ) diff --git a/miden-lib/src/tests/test_prologue.rs b/miden-lib/src/tests/test_prologue.rs index 4d6273daa..2af7e65db 100644 --- a/miden-lib/src/tests/test_prologue.rs +++ b/miden-lib/src/tests/test_prologue.rs @@ -61,7 +61,7 @@ fn test_transaction_prologue() { Some(assembly_file), ); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -363,7 +363,7 @@ pub fn test_prologue_create_account() { ); let _process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -404,7 +404,7 @@ pub fn test_prologue_create_account_valid_fungible_faucet_reserved_slot() { ); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ); @@ -445,7 +445,7 @@ pub fn test_prologue_create_account_invalid_fungible_faucet_reserved_slot() { ); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ); @@ -486,7 +486,7 @@ pub fn test_prologue_create_account_valid_non_fungible_faucet_reserved_slot() { ); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ); @@ -527,7 +527,7 @@ pub fn test_prologue_create_account_invalid_non_fungible_faucet_reserved_slot() ); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ); @@ -570,7 +570,7 @@ pub fn test_prologue_create_account_invalid_seed() { .unwrap(); let process = - run_tx(transaction.tx_program().clone(), transaction.stack_inputs(), advice_provider); + run_tx(transaction.program().clone(), transaction.stack_inputs(), advice_provider); assert!(process.is_err()); } @@ -592,7 +592,7 @@ fn test_get_blk_version() { prepare_transaction(account, None, block_header, chain, notes, None, code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -619,7 +619,7 @@ fn test_get_blk_timestamp() { prepare_transaction(account, None, block_header, chain, notes, None, code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) diff --git a/miden-lib/src/tests/test_tx.rs b/miden-lib/src/tests/test_tx.rs index faa904e99..f2e3ee438 100644 --- a/miden-lib/src/tests/test_tx.rs +++ b/miden-lib/src/tests/test_tx.rs @@ -47,7 +47,7 @@ fn test_create_note() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) @@ -216,7 +216,7 @@ fn test_get_output_notes_hash() { prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); let _process = run_tx( - transaction.tx_program().clone(), + transaction.program().clone(), transaction.stack_inputs(), MemAdviceProvider::from(transaction.advice_provider_inputs()), ) diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index 2e8557715..865eeff90 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -131,7 +131,7 @@ impl TransactionExecutor { let advice_recorder: RecAdviceProvider = transaction.advice_provider_inputs().into(); let mut host = TransactionHost::new(advice_recorder); let result = vm_processor::execute( - transaction.tx_program(), + transaction.program(), transaction.stack_inputs(), &mut host, Default::default(), diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index a8a50597e..6a5a3638b 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -37,7 +37,7 @@ impl TransactionProver { let advice_provider: MemAdviceProvider = transaction.advice_provider_inputs().into(); let mut host = TransactionHost::new(advice_provider); let (outputs, proof) = prove( - transaction.tx_program(), + transaction.program(), transaction.stack_inputs(), &mut host, self.proof_options.clone(), diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index ab8385b89..84ab013ab 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -18,6 +18,7 @@ use crate::{ // ================================================================================================ /// Contains the data required to execute a transaction. +#[derive(Debug, Clone, PartialEq, Eq)] pub struct TransactionInputs { pub account: Account, pub account_seed: Option, diff --git a/objects/src/transaction/prepared_tx.rs b/objects/src/transaction/prepared_tx.rs index 146f5cac4..9d7d733fc 100644 --- a/objects/src/transaction/prepared_tx.rs +++ b/objects/src/transaction/prepared_tx.rs @@ -10,22 +10,14 @@ use crate::accounts::validate_account_seed; /// A struct that contains all of the data required to execute a transaction. /// /// This includes: -/// - account: Account that the transaction is being executed against. -/// - account_seed: An optional account seed used to create a new account. -/// - block_header: The header of the latest known block. -/// - block_chain: The chain MMR associated with the latest known block. -/// - input_notes: A vector of notes consumed by the transaction. -/// - tx_script: An optional transaction script. -/// - tx_program: The transaction program. +/// - A an executable program which defines the transaction. +/// - An optional transaction script. +/// - A set of inputs against which the transaction program should be executed. #[derive(Debug)] pub struct PreparedTransaction { - account: Account, - account_seed: Option, - block_header: BlockHeader, - block_chain: ChainMmr, - input_notes: InputNotes, + program: Program, tx_script: Option, - tx_program: Program, + tx_inputs: TransactionInputs, } impl PreparedTransaction { @@ -38,39 +30,36 @@ impl PreparedTransaction { tx_script: Option, tx_inputs: TransactionInputs, ) -> Result { - Self::validate_new_account_seed(&tx_inputs.account, tx_inputs.account_seed)?; - Ok(Self { - account: tx_inputs.account, - account_seed: tx_inputs.account_seed, - block_header: tx_inputs.block_header, - block_chain: tx_inputs.block_chain, - input_notes: tx_inputs.input_notes, - tx_script, - tx_program: program, - }) + validate_new_account_seed(&tx_inputs.account, tx_inputs.account_seed)?; + Ok(Self { program, tx_script, tx_inputs }) } // ACCESSORS // -------------------------------------------------------------------------------------------- + /// Returns the transaction program. + pub fn program(&self) -> &Program { + &self.program + } + /// Returns the account. pub fn account(&self) -> &Account { - &self.account + &self.tx_inputs.account } /// Returns the block header. pub fn block_header(&self) -> &BlockHeader { - &self.block_header + &self.tx_inputs.block_header } /// Returns the block chain. pub fn block_chain(&self) -> &ChainMmr { - &self.block_chain + &self.tx_inputs.block_chain } /// Returns the input notes. pub fn input_notes(&self) -> &InputNotes { - &self.input_notes + &self.tx_inputs.input_notes } /// Return a reference the transaction script. @@ -78,55 +67,33 @@ impl PreparedTransaction { &self.tx_script } - /// Returns the transaction program. - pub fn tx_program(&self) -> &Program { - &self.tx_program - } - /// Returns the stack inputs required when executing the transaction. pub fn stack_inputs(&self) -> StackInputs { - let initial_acct_hash = if self.account.is_new() { + let initial_acct_hash = if self.account().is_new() { Digest::default() } else { - self.account.hash() + self.account().hash() }; utils::generate_stack_inputs( - &self.account.id(), + &self.account().id(), initial_acct_hash, - self.input_notes.commitment(), - &self.block_header, + self.input_notes().commitment(), + self.block_header(), ) } /// Returns the advice inputs required when executing the transaction. pub fn advice_provider_inputs(&self) -> AdviceInputs { utils::generate_advice_provider_inputs( - &self.account, - self.account_seed, - &self.block_header, - &self.block_chain, - &self.input_notes, + self.account(), + self.tx_inputs.account_seed, + self.block_header(), + self.block_chain(), + self.input_notes(), &self.tx_script, ) } - // HELPERS - // -------------------------------------------------------------------------------------------- - - /// Validates that a valid account seed has been provided if the account the transaction is - /// being executed against is new. - fn validate_new_account_seed( - account: &Account, - seed: Option, - ) -> Result<(), PreparedTransactionError> { - match (account.is_new(), seed) { - (true, Some(seed)) => validate_account_seed(account, seed) - .map_err(PreparedTransactionError::InvalidAccountIdSeedError), - (true, None) => Err(PreparedTransactionError::AccountIdSeedNoteProvided), - _ => Ok(()), - } - } - // CONSUMERS // -------------------------------------------------------------------------------------------- @@ -135,12 +102,29 @@ impl PreparedTransaction { self, ) -> (Account, BlockHeader, ChainMmr, InputNotes, Program, Option) { ( - self.account, - self.block_header, - self.block_chain, - self.input_notes, - self.tx_program, + self.tx_inputs.account, + self.tx_inputs.block_header, + self.tx_inputs.block_chain, + self.tx_inputs.input_notes, + self.program, self.tx_script, ) } } + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Validates that a valid account seed has been provided if the account the transaction is +/// being executed against is new. +fn validate_new_account_seed( + account: &Account, + seed: Option, +) -> Result<(), PreparedTransactionError> { + match (account.is_new(), seed) { + (true, Some(seed)) => validate_account_seed(account, seed) + .map_err(PreparedTransactionError::InvalidAccountIdSeedError), + (true, None) => Err(PreparedTransactionError::AccountIdSeedNoteProvided), + _ => Ok(()), + } +} From dc0289434999d4072e68ed7569431f418ee698f3 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Dec 2023 10:57:24 -0800 Subject: [PATCH 04/21] refactor: use TransactionInputs in ExecutedTransaction --- miden-lib/src/tests/test_account.rs | 2 +- miden-lib/src/tests/test_epilogue.rs | 12 ++--- miden-tx/src/prover/mod.rs | 8 ++-- mock/src/mock/transaction.rs | 26 +++++------ objects/src/transaction/executed_tx.rs | 64 +++++++------------------- objects/src/transaction/inputs.rs | 10 ++-- objects/src/transaction/prepared_tx.rs | 25 ++-------- objects/src/transaction/utils.rs | 58 ++++++++++++----------- 8 files changed, 81 insertions(+), 124 deletions(-) diff --git a/miden-lib/src/tests/test_account.rs b/miden-lib/src/tests/test_account.rs index 2b0c58b80..9332278c5 100644 --- a/miden-lib/src/tests/test_account.rs +++ b/miden-lib/src/tests/test_account.rs @@ -67,7 +67,7 @@ pub fn test_set_code_succeeds() { let executed_transaction = mock_executed_tx(AssetPreservationStatus::Preserved); let created_notes_data_procedure = - created_notes_data_procedure(executed_transaction.created_notes()); + created_notes_data_procedure(executed_transaction.output_notes()); let code = format!( " diff --git a/miden-lib/src/tests/test_epilogue.rs b/miden-lib/src/tests/test_epilogue.rs index 5033af199..1d1263c04 100644 --- a/miden-lib/src/tests/test_epilogue.rs +++ b/miden-lib/src/tests/test_epilogue.rs @@ -21,7 +21,7 @@ fn test_epilogue() { let executed_transaction = mock_executed_tx(AssetPreservationStatus::Preserved); let created_notes_data_procedure = - created_notes_data_procedure(executed_transaction.created_notes()); + created_notes_data_procedure(executed_transaction.output_notes()); let imports = "use.miden::sat::internal::prologue\n"; let code = format!( @@ -81,9 +81,9 @@ fn test_compute_created_note_hash() { let executed_transaction = mock_executed_tx(AssetPreservationStatus::Preserved); let created_notes_data_procedure = - created_notes_data_procedure(executed_transaction.created_notes()); + created_notes_data_procedure(executed_transaction.output_notes()); - for (note, i) in executed_transaction.created_notes().iter().zip(0u32..) { + for (note, i) in executed_transaction.output_notes().iter().zip(0u32..) { let imports = "use.miden::sat::internal::prologue\n"; let test = format!( " @@ -132,7 +132,7 @@ fn test_epilogue_asset_preservation_violation() { let executed_transaction = mock_executed_tx(asset_preservation); let created_notes_data_procedure = - created_notes_data_procedure(executed_transaction.created_notes()); + created_notes_data_procedure(executed_transaction.output_notes()); let imports = "use.miden::sat::internal::prologue\n"; let code = format!( @@ -166,7 +166,7 @@ fn test_epilogue_increment_nonce_success() { let executed_transaction = mock_executed_tx(AssetPreservationStatus::Preserved); let created_notes_data_procedure = - created_notes_data_procedure(executed_transaction.created_notes()); + created_notes_data_procedure(executed_transaction.output_notes()); let imports = "use.miden::sat::internal::prologue\n"; let code = format!( @@ -198,7 +198,7 @@ fn test_epilogue_increment_nonce_violation() { let executed_transaction = mock_executed_tx(AssetPreservationStatus::Preserved); let created_notes_data_procedure = - created_notes_data_procedure(executed_transaction.created_notes()); + created_notes_data_procedure(executed_transaction.output_notes()); let imports = "use.miden::sat::internal::prologue\n"; let code = format!( diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 6a5a3638b..2a7f94ac5 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -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 = OutputNotes::try_from_vm_result(&outputs, &stack, &map, &store) + let output_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.envelopes().collect(), + output_notes.envelopes().collect(), tx_script.map(|tx_script| *tx_script.hash()), block_header.hash(), proof, @@ -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 = OutputNotes::try_from_vm_result(&outputs, &stack, &map, &store) + let output_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.envelopes().collect(), + output_notes.envelopes().collect(), tx_script_root, block_hash, proof, diff --git a/mock/src/mock/transaction.rs b/mock/src/mock/transaction.rs index 1ff17c7a7..8477b50f7 100644 --- a/mock/src/mock/transaction.rs +++ b/mock/src/mock/transaction.rs @@ -2,7 +2,7 @@ use miden_lib::assembler::assembler; use miden_objects::{ accounts::Account, notes::Note, - transaction::{ChainMmr, ExecutedTransaction, InputNote}, + transaction::{ChainMmr, ExecutedTransaction, InputNote, InputNotes, TransactionInputs}, utils::collections::Vec, BlockHeader, Felt, FieldElement, }; @@ -109,26 +109,24 @@ pub fn mock_executed_tx(asset_preservation: AssetPreservationStatus) -> Executed let (consumed_notes, created_notes) = mock_notes(&assembler, &asset_preservation); // Chain data - let (chain_mmr, recorded_notes) = mock_chain_data(consumed_notes); + let (block_chain, input_notes) = mock_chain_data(consumed_notes); // Block header let block_header = mock_block_header( 4, - Some(chain_mmr.peaks().hash_peaks()), + Some(block_chain.peaks().hash_peaks()), None, &[initial_account.clone()], ); - // Executed Transaction - ExecutedTransaction::new( - initial_account, - None, - final_account, - recorded_notes, - created_notes, - None, + let tx_inputs = TransactionInputs { + account: initial_account, + account_seed: None, block_header, - chain_mmr, - ) - .unwrap() + block_chain, + input_notes: InputNotes::new(input_notes).unwrap(), + }; + + // Executed Transaction + ExecutedTransaction::new(tx_inputs, final_account, created_notes, None).unwrap() } diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index c438e894b..bc5df64d8 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -1,56 +1,43 @@ use vm_core::StackOutputs; -use super::TransactionScript; +use super::{TransactionInputs, TransactionScript}; use crate::{ accounts::validate_account_seed, - transaction::{ - utils, Account, AdviceInputs, BlockHeader, ChainMmr, Digest, InputNote, InputNotes, Note, - StackInputs, Vec, Word, - }, + transaction::{utils, Account, AdviceInputs, Digest, InputNotes, Note, StackInputs, Vec, Word}, ExecutedTransactionError, }; +// EXECUTED TRANSACTION +// ================================================================================================ + #[derive(Debug)] pub struct ExecutedTransaction { - initial_account: Account, - initial_account_seed: Option, + tx_inputs: TransactionInputs, final_account: Account, - consumed_notes: InputNotes, created_notes: Vec, tx_script: Option, - block_header: BlockHeader, - block_chain: ChainMmr, } impl ExecutedTransaction { /// Constructs a new [ExecutedTransaction] instance. - #[allow(clippy::too_many_arguments)] pub fn new( - initial_account: Account, - initial_account_seed: Option, + tx_inputs: TransactionInputs, final_account: Account, - consumed_notes: Vec, created_notes: Vec, tx_script: Option, - block_header: BlockHeader, - block_chain: ChainMmr, ) -> Result { - Self::validate_new_account_seed(&initial_account, initial_account_seed)?; + Self::validate_new_account_seed(&tx_inputs.account, tx_inputs.account_seed)?; Ok(Self { - initial_account, - initial_account_seed, + tx_inputs, final_account, - consumed_notes: InputNotes::new(consumed_notes).unwrap(), created_notes, tx_script, - block_header, - block_chain, }) } /// Returns the initial account. pub fn initial_account(&self) -> &Account { - &self.initial_account + &self.tx_inputs.account } /// Returns the final account. @@ -59,12 +46,12 @@ impl ExecutedTransaction { } /// Returns the consumed notes. - pub fn consumed_notes(&self) -> &InputNotes { - &self.consumed_notes + pub fn input_notes(&self) -> &InputNotes { + &self.tx_inputs.input_notes } /// Returns the created notes. - pub fn created_notes(&self) -> &[Note] { + pub fn output_notes(&self) -> &[Note] { &self.created_notes } @@ -75,39 +62,22 @@ impl ExecutedTransaction { /// Returns the block hash. pub fn block_hash(&self) -> Digest { - self.block_header.hash() + self.tx_inputs.block_header.hash() } /// Returns the stack inputs required when executing the transaction. pub fn stack_inputs(&self) -> StackInputs { - let initial_acct_hash = if self.initial_account.is_new() { - Digest::default() - } else { - self.initial_account.hash() - }; - utils::generate_stack_inputs( - &self.initial_account.id(), - initial_acct_hash, - self.consumed_notes.commitment(), - &self.block_header, - ) + utils::generate_stack_inputs(&self.tx_inputs) } /// Returns the consumed notes commitment. pub fn consumed_notes_commitment(&self) -> Digest { - self.consumed_notes.commitment() + self.input_notes().commitment() } /// Returns the advice inputs required when executing the transaction. pub fn advice_provider_inputs(&self) -> AdviceInputs { - utils::generate_advice_provider_inputs( - &self.initial_account, - self.initial_account_seed, - &self.block_header, - &self.block_chain, - &self.consumed_notes, - &self.tx_script, - ) + utils::generate_advice_provider_inputs(&self.tx_inputs, &self.tx_script) } /// Returns the stack outputs produced as a result of executing a transaction. diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index 84ab013ab..f8f531df9 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -217,7 +217,7 @@ pub fn build_input_notes_commitment>(nullifiers: I Hasher::hash_elements(&elements) } -// RECORDED NOTE +// INPUT NOTE // ================================================================================================ /// An input note for a transaction. @@ -229,22 +229,22 @@ pub struct InputNote { } impl InputNote { - /// Returns a new instance of a [RecordedNote] with the specified note and origin. + /// Returns a new instance of an [InputNote] with the specified note and proof. pub fn new(note: Note, proof: NoteInclusionProof) -> Self { Self { note, proof } } - /// Returns a reference to the note which was recorded. + /// Returns a reference to the underlying note. pub fn note(&self) -> &Note { &self.note } - /// Returns a reference to the inclusion proof of the recorded note. + /// Returns a reference to the inclusion proof of the note. pub fn proof(&self) -> &NoteInclusionProof { &self.proof } - /// Returns a reference to the origin of the recorded note. + /// Returns a reference to the origin of the note. pub fn origin(&self) -> &NoteOrigin { self.proof.origin() } diff --git a/objects/src/transaction/prepared_tx.rs b/objects/src/transaction/prepared_tx.rs index 9d7d733fc..f92f1b251 100644 --- a/objects/src/transaction/prepared_tx.rs +++ b/objects/src/transaction/prepared_tx.rs @@ -1,6 +1,6 @@ use super::{ - utils, Account, AdviceInputs, BlockHeader, ChainMmr, Digest, InputNotes, - PreparedTransactionError, Program, StackInputs, TransactionInputs, TransactionScript, Word, + utils, Account, AdviceInputs, BlockHeader, ChainMmr, InputNotes, PreparedTransactionError, + Program, StackInputs, TransactionInputs, TransactionScript, Word, }; use crate::accounts::validate_account_seed; @@ -69,29 +69,12 @@ impl PreparedTransaction { /// Returns the stack inputs required when executing the transaction. pub fn stack_inputs(&self) -> StackInputs { - let initial_acct_hash = if self.account().is_new() { - Digest::default() - } else { - self.account().hash() - }; - utils::generate_stack_inputs( - &self.account().id(), - initial_acct_hash, - self.input_notes().commitment(), - self.block_header(), - ) + utils::generate_stack_inputs(&self.tx_inputs) } /// Returns the advice inputs required when executing the transaction. pub fn advice_provider_inputs(&self) -> AdviceInputs { - utils::generate_advice_provider_inputs( - self.account(), - self.tx_inputs.account_seed, - self.block_header(), - self.block_chain(), - self.input_notes(), - &self.tx_script, - ) + utils::generate_advice_provider_inputs(&self.tx_inputs, &self.tx_script) } // CONSUMERS diff --git a/objects/src/transaction/utils.rs b/objects/src/transaction/utils.rs index 0efd2292d..880f33501 100644 --- a/objects/src/transaction/utils.rs +++ b/objects/src/transaction/utils.rs @@ -1,10 +1,13 @@ use vm_core::utils::IntoBytes; use super::{ - Account, AccountId, AdviceInputs, BlockHeader, ChainMmr, Digest, Felt, Hasher, InputNotes, - Note, StackInputs, StackOutputs, ToAdviceInputs, TransactionScript, Vec, Word, ZERO, + AdviceInputs, Digest, Felt, Hasher, Note, StackInputs, StackOutputs, ToAdviceInputs, + TransactionInputs, TransactionScript, Vec, Word, ZERO, }; +// ADVICE INPUT CONSTRUCTORS +// ================================================================================================ + /// Returns the advice inputs required when executing a transaction. /// This includes the initial account, an optional account seed (required for new accounts), the /// number of consumed notes, the core consumed note data, and the consumed note inputs. @@ -46,26 +49,22 @@ use super::{ /// - PEAK_N is the n'th peak in the block chain MMR from the last known block. /// - ACT_ID_SEED3..0 is the account id seed. pub fn generate_advice_provider_inputs( - account: &Account, - account_id_seed: Option, - block_header: &BlockHeader, - block_chain: &ChainMmr, - notes: &InputNotes, + tx_inputs: &TransactionInputs, tx_script: &Option, ) -> AdviceInputs { let mut advice_inputs = AdviceInputs::default(); // insert block data - block_header.to_advice_inputs(&mut advice_inputs); + (&tx_inputs.block_header).to_advice_inputs(&mut advice_inputs); // insert block chain mmr - block_chain.to_advice_inputs(&mut advice_inputs); + (&tx_inputs.block_chain).to_advice_inputs(&mut advice_inputs); // insert account data - account.to_advice_inputs(&mut advice_inputs); + tx_inputs.account.to_advice_inputs(&mut advice_inputs); // insert consumed notes data to advice stack - notes.to_advice_inputs(&mut advice_inputs); + (tx_inputs.input_notes).to_advice_inputs(&mut advice_inputs); if let Some(tx_script) = tx_script.as_ref() { // populate the advice inputs with the transaction script data @@ -77,9 +76,9 @@ pub fn generate_advice_provider_inputs( } // insert account id seed into advice map - if let Some(seed) = account_id_seed { + if let Some(seed) = tx_inputs.account_seed { advice_inputs.extend_map(vec![( - [account.id().into(), ZERO, ZERO, ZERO].into_bytes(), + [tx_inputs.account.id().into(), ZERO, ZERO, ZERO].into_bytes(), seed.to_vec(), )]); } @@ -87,9 +86,12 @@ pub fn generate_advice_provider_inputs( advice_inputs } +// INPUT STACK CONSTRUCTOR +// ================================================================================================ + /// Returns the stack inputs required when executing a transaction. -/// This includes the consumed notes commitment, the account hash, the account id, and the block -/// reference. +/// +/// This includes the input notes commitment, the account hash, the account id, and the block hash. /// /// Stack: [BH, acct_id, IAH, NC] /// @@ -97,21 +99,25 @@ pub fn generate_advice_provider_inputs( /// - acct_id is the account id of the account that the transaction is being executed against. /// - IAH is the initial account hash of the account that the transaction is being executed against. /// - NC is the nullifier commitment of the transaction. This is a sequential hash of all -/// (nullifier, script_root) pairs for the notes consumed in the transaction. -pub fn generate_stack_inputs( - account_id: &AccountId, - account_hash: Digest, - consumed_notes_commitment: Digest, - block_header: &BlockHeader, -) -> StackInputs { +/// (nullifier, ZERO) tuples for the notes consumed in the transaction. +pub fn generate_stack_inputs(tx_inputs: &TransactionInputs) -> StackInputs { + let initial_acct_hash = if tx_inputs.account.is_new() { + Digest::default() + } else { + tx_inputs.account.hash() + }; + let mut inputs: Vec = Vec::with_capacity(13); - inputs.extend(*consumed_notes_commitment); - inputs.extend_from_slice(account_hash.as_elements()); - inputs.push((*account_id).into()); - inputs.extend_from_slice(block_header.hash().as_elements()); + inputs.extend(tx_inputs.input_notes.commitment()); + inputs.extend_from_slice(initial_acct_hash.as_elements()); + inputs.push((tx_inputs.account.id()).into()); + inputs.extend_from_slice(tx_inputs.block_header.hash().as_elements()); StackInputs::new(inputs) } +// OUTPUT STACK CONSTRUCTOR +// ================================================================================================ + /// Returns the stack outputs produced as a result of executing a transaction. This includes the /// final account hash and created notes commitment. /// From 424adf712f6d3373a3657c4484a86da5656f805c Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Dec 2023 11:19:33 -0800 Subject: [PATCH 05/21] refactor: construct TransactionResult with TransactionInputs --- miden-tx/src/executor/mod.rs | 24 +++++++++--------------- miden-tx/src/prover/mod.rs | 11 +++++------ objects/src/transaction/prepared_tx.rs | 13 ++----------- objects/src/transaction/tx_result.rs | 19 +++++++++---------- 4 files changed, 25 insertions(+), 42 deletions(-) diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index 865eeff90..f9d757f08 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -1,8 +1,8 @@ use miden_lib::{outputs::TX_SCRIPT_ROOT_WORD_IDX, transaction::extract_account_storage_delta}; use miden_objects::{ - accounts::{Account, AccountDelta}, + accounts::AccountDelta, assembly::ProgramAst, - transaction::{FinalAccountStub, InputNotes, OutputNotes, TransactionScript}, + transaction::{FinalAccountStub, OutputNotes, TransactionInputs, TransactionScript}, Felt, TransactionResultError, Word, WORD_SIZE, }; use vm_core::{Program, StackOutputs, StarkField}; @@ -138,16 +138,13 @@ impl TransactionExecutor { ) .map_err(TransactionExecutorError::ExecuteTransactionProgramFailed)?; - let (account, block_header, _block_chain, consumed_notes, tx_program, tx_script) = - transaction.into_parts(); + let (tx_program, tx_script, tx_inputs) = transaction.into_parts(); let (advice_recorder, event_handler) = host.into_parts(); create_transaction_result( - account, - consumed_notes, - block_header.hash(), tx_program, tx_script.map(|s| *s.hash()), + tx_inputs, advice_recorder, result.stack_outputs().clone(), event_handler, @@ -189,14 +186,11 @@ impl TransactionExecutor { } } -#[allow(clippy::too_many_arguments)] /// Creates a new [TransactionResult] from the provided data, advice provider and stack outputs. pub fn create_transaction_result( - initial_account: Account, - input_notes: InputNotes, - block_hash: Digest, program: Program, tx_script_root: Option, + tx_inputs: TransactionInputs, advice_provider: RecAdviceProvider, stack_outputs: StackOutputs, event_handler: EventHandler, @@ -220,10 +214,12 @@ pub fn create_transaction_result( [TX_SCRIPT_ROOT_WORD_IDX * WORD_SIZE..(TX_SCRIPT_ROOT_WORD_IDX + 1) * WORD_SIZE] ); + let initial_account = &tx_inputs.account; + // TODO: Fix delta extraction for new account creation // extract the account storage delta let storage_delta = - extract_account_storage_delta(&store, &initial_account, &final_account_stub)?; + extract_account_storage_delta(&store, initial_account, &final_account_stub)?; // extract the nonce delta let nonce_delta = if initial_account.nonce() != final_account_stub.0.nonce() { @@ -240,12 +236,10 @@ pub fn create_transaction_result( AccountDelta::new(storage_delta, vault_delta, nonce_delta).expect("invalid account delta"); TransactionResult::new( - initial_account, + tx_inputs, final_account_stub, account_delta, - input_notes, output_notes, - block_hash, program, tx_script_root, advice_witness, diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 2a7f94ac5..9146ba9f0 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -53,17 +53,16 @@ impl TransactionProver { let output_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) = - transaction.into_parts(); + let (_tx_program, tx_script, tx_inputs) = transaction.into_parts(); Ok(ProvenTransaction::new( - account.id(), - account.hash(), + tx_inputs.account.id(), + tx_inputs.account.hash(), final_account_stub.0.hash(), - input_notes.nullifiers().collect(), + tx_inputs.input_notes.nullifiers().collect(), output_notes.envelopes().collect(), tx_script.map(|tx_script| *tx_script.hash()), - block_header.hash(), + tx_inputs.block_header.hash(), proof, )) } diff --git a/objects/src/transaction/prepared_tx.rs b/objects/src/transaction/prepared_tx.rs index f92f1b251..6eb35e18f 100644 --- a/objects/src/transaction/prepared_tx.rs +++ b/objects/src/transaction/prepared_tx.rs @@ -81,17 +81,8 @@ impl PreparedTransaction { // -------------------------------------------------------------------------------------------- /// Consumes the prepared transaction and returns its parts. - pub fn into_parts( - self, - ) -> (Account, BlockHeader, ChainMmr, InputNotes, Program, Option) { - ( - self.tx_inputs.account, - self.tx_inputs.block_header, - self.tx_inputs.block_chain, - self.tx_inputs.input_notes, - self.program, - self.tx_script, - ) + pub fn into_parts(self) -> (Program, Option, TransactionInputs) { + (self.program, self.tx_script, self.tx_inputs) } } diff --git a/objects/src/transaction/tx_result.rs b/objects/src/transaction/tx_result.rs index 1961a77b8..49e0686eb 100644 --- a/objects/src/transaction/tx_result.rs +++ b/objects/src/transaction/tx_result.rs @@ -1,8 +1,10 @@ use vm_processor::{AdviceInputs, Program}; use crate::{ - accounts::{Account, AccountDelta, AccountId}, - transaction::{FinalAccountStub, InputNotes, OutputNotes, TransactionWitness}, + accounts::{AccountDelta, AccountId}, + transaction::{ + FinalAccountStub, InputNotes, OutputNotes, TransactionInputs, TransactionWitness, + }, Digest, TransactionResultError, }; @@ -40,26 +42,23 @@ impl TransactionResult { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- /// Creates a new [TransactionResult] from the provided data, advice provider and stack outputs. - #[allow(clippy::too_many_arguments)] pub fn new( - initial_account: Account, + tx_inputs: TransactionInputs, final_account_stub: FinalAccountStub, account_delta: AccountDelta, - input_notes: InputNotes, output_notes: OutputNotes, - block_hash: Digest, program: Program, tx_script_root: Option, advice_witness: AdviceInputs, ) -> Result { Ok(Self { - account_id: initial_account.id(), - initial_account_hash: initial_account.hash(), + account_id: tx_inputs.account.id(), + initial_account_hash: tx_inputs.account.hash(), final_account_hash: final_account_stub.0.hash(), account_delta, - input_notes, + input_notes: tx_inputs.input_notes, output_notes, - block_hash, + block_hash: tx_inputs.block_header.hash(), program, tx_script_root, advice_witness, From 9e21d5a47e899faa3967d0782fa60d5800029799 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Dec 2023 11:51:00 -0800 Subject: [PATCH 06/21] refactor: added TransactionOutputs --- miden-lib/src/transaction/account_stub.rs | 5 +- miden-tx/src/executor/mod.rs | 17 +++--- miden-tx/src/prover/mod.rs | 20 +++---- miden-tx/src/result.rs | 72 ++++++++++++++--------- miden-tx/src/tests.rs | 13 ++-- objects/src/accounts/stub.rs | 2 +- objects/src/transaction/account_stub.rs | 6 -- objects/src/transaction/mod.rs | 4 +- objects/src/transaction/outputs.rs | 11 ++++ objects/src/transaction/tx_result.rs | 9 ++- 10 files changed, 82 insertions(+), 77 deletions(-) delete mode 100644 objects/src/transaction/account_stub.rs diff --git a/miden-lib/src/transaction/account_stub.rs b/miden-lib/src/transaction/account_stub.rs index b3556ed19..21cdbcb98 100644 --- a/miden-lib/src/transaction/account_stub.rs +++ b/miden-lib/src/transaction/account_stub.rs @@ -1,7 +1,6 @@ use miden_objects::{ accounts::{Account, AccountId, AccountStorage, AccountStorageDelta, AccountStub}, crypto::merkle::{merkle_tree_delta, MerkleStore}, - transaction::FinalAccountStub, AccountError, TransactionResultError, Word, }; @@ -33,12 +32,12 @@ pub fn parse_final_account_stub(elements: &[Word]) -> Result Result { // extract storage slots delta let tree_delta = merkle_tree_delta( initial_account.storage().root(), - final_account_stub.0.storage_root(), + final_account_stub.storage_root(), AccountStorage::STORAGE_TREE_DEPTH, store, ) diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index f9d757f08..835febd25 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::AccountDelta, assembly::ProgramAst, - transaction::{FinalAccountStub, OutputNotes, TransactionInputs, TransactionScript}, + transaction::{TransactionInputs, TransactionOutputs, TransactionScript}, Felt, TransactionResultError, Word, WORD_SIZE, }; use vm_core::{Program, StackOutputs, StarkField}; @@ -199,9 +199,8 @@ pub fn create_transaction_result( let (advice_witness, stack, map, store) = advice_provider.finalize(); // parse transaction results - let final_account_stub = - FinalAccountStub::try_from_vm_result(&stack_outputs, &stack, &map, &store)?; - let output_notes = OutputNotes::try_from_vm_result(&stack_outputs, &stack, &map, &store)?; + let tx_outputs = TransactionOutputs::try_from_vm_result(&stack_outputs, &stack, &map, &store)?; + let final_account = &tx_outputs.account; // assert the tx_script_root is consistent with the output stack debug_assert_eq!( @@ -218,12 +217,11 @@ pub fn create_transaction_result( // TODO: Fix delta extraction for new account creation // extract the account storage delta - let storage_delta = - extract_account_storage_delta(&store, initial_account, &final_account_stub)?; + let storage_delta = extract_account_storage_delta(&store, initial_account, final_account)?; // extract the nonce delta - let nonce_delta = if initial_account.nonce() != final_account_stub.0.nonce() { - Some(final_account_stub.0.nonce()) + let nonce_delta = if initial_account.nonce() != final_account.nonce() { + Some(final_account.nonce()) } else { None }; @@ -237,9 +235,8 @@ pub fn create_transaction_result( TransactionResult::new( tx_inputs, - final_account_stub, + tx_outputs, account_delta, - output_notes, program, tx_script_root, advice_witness, diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 9146ba9f0..1af078dd7 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -1,5 +1,5 @@ use miden_objects::transaction::{ - FinalAccountStub, OutputNotes, PreparedTransaction, ProvenTransaction, TransactionWitness, + PreparedTransaction, ProvenTransaction, TransactionOutputs, TransactionWitness, }; use miden_prover::prove; pub use miden_prover::ProvingOptions; @@ -47,10 +47,7 @@ impl TransactionProver { // extract transaction outputs and process transaction data let (advice_provider, _event_handler) = host.into_parts(); let (stack, map, store) = advice_provider.into_parts(); - let final_account_stub = - FinalAccountStub::try_from_vm_result(&outputs, &stack, &map, &store) - .map_err(TransactionProverError::TransactionResultError)?; - let output_notes = OutputNotes::try_from_vm_result(&outputs, &stack, &map, &store) + let tx_outputs = TransactionOutputs::try_from_vm_result(&outputs, &stack, &map, &store) .map_err(TransactionProverError::TransactionResultError)?; let (_tx_program, tx_script, tx_inputs) = transaction.into_parts(); @@ -58,9 +55,9 @@ impl TransactionProver { Ok(ProvenTransaction::new( tx_inputs.account.id(), tx_inputs.account.hash(), - final_account_stub.0.hash(), + tx_outputs.account.hash(), tx_inputs.input_notes.nullifiers().collect(), - output_notes.envelopes().collect(), + tx_outputs.output_notes.envelopes().collect(), tx_script.map(|tx_script| *tx_script.hash()), tx_inputs.block_header.hash(), proof, @@ -101,18 +98,15 @@ impl TransactionProver { // extract transaction outputs and process transaction data let (advice_provider, _event_handler) = host.into_parts(); let (stack, map, store) = advice_provider.into_parts(); - let final_account_stub = - FinalAccountStub::try_from_vm_result(&outputs, &stack, &map, &store) - .map_err(TransactionProverError::TransactionResultError)?; - let output_notes = OutputNotes::try_from_vm_result(&outputs, &stack, &map, &store) + let tx_outputs = TransactionOutputs::try_from_vm_result(&outputs, &stack, &map, &store) .map_err(TransactionProverError::TransactionResultError)?; Ok(ProvenTransaction::new( account_id, initial_account_hash, - final_account_stub.0.hash(), + tx_outputs.account.hash(), consumed_notes_info, - output_notes.envelopes().collect(), + tx_outputs.output_notes.envelopes().collect(), tx_script_root, block_hash, proof, diff --git a/miden-tx/src/result.rs b/miden-tx/src/result.rs index fcd945924..9d39a9ba4 100644 --- a/miden-tx/src/result.rs +++ b/miden-tx/src/result.rs @@ -5,8 +5,9 @@ use miden_lib::{ transaction::parse_final_account_stub, }; use miden_objects::{ + accounts::AccountStub, crypto::merkle::MerkleStore, - transaction::{FinalAccountStub, OutputNote, OutputNotes}, + transaction::{OutputNote, OutputNotes, TransactionOutputs}, utils::collections::{BTreeMap, Vec}, Digest, Felt, TransactionResultError, Word, WORD_SIZE, }; @@ -26,6 +27,23 @@ pub trait TryFromVmResult: Sized { ) -> Result; } +impl TryFromVmResult for TransactionOutputs { + type Error = TransactionResultError; + + fn try_from_vm_result( + stack_outputs: &StackOutputs, + advice_stack: &[Felt], + advice_map: &BTreeMap<[u8; 32], Vec>, + merkle_store: &MerkleStore, + ) -> Result { + let account = extract_final_account_stub(stack_outputs, advice_map)?; + let output_notes = + OutputNotes::try_from_vm_result(stack_outputs, advice_stack, advice_map, merkle_store)?; + + Ok(Self { account, output_notes }) + } +} + impl TryFromVmResult for OutputNotes { type Error = TransactionResultError; @@ -74,35 +92,31 @@ impl TryFromVmResult for OutputNotes { } } -impl TryFromVmResult for FinalAccountStub { - type Error = TransactionResultError; +// HELPER FUNCTIONS +// ================================================================================================ - fn try_from_vm_result( - stack_outputs: &StackOutputs, - _advice_stack: &[Felt], - advice_map: &BTreeMap<[u8; 32], Vec>, - _merkle_store: &MerkleStore, - ) -> Result { - let final_account_hash: Word = - stack_outputs.stack()[FINAL_ACCOUNT_HASH_WORD_IDX * WORD_SIZE - ..(FINAL_ACCOUNT_HASH_WORD_IDX + 1) * WORD_SIZE] - .iter() - .rev() - .map(|x| Felt::from(*x)) - .collect::>() - .try_into() - .expect("word size is correct"); - let final_account_hash: Digest = final_account_hash.into(); +fn extract_final_account_stub( + stack_outputs: &StackOutputs, + advice_map: &BTreeMap<[u8; 32], Vec>, +) -> Result { + let final_account_hash: Word = stack_outputs.stack() + [FINAL_ACCOUNT_HASH_WORD_IDX * WORD_SIZE..(FINAL_ACCOUNT_HASH_WORD_IDX + 1) * WORD_SIZE] + .iter() + .rev() + .map(|x| Felt::from(*x)) + .collect::>() + .try_into() + .expect("word size is correct"); + let final_account_hash: Digest = final_account_hash.into(); - // extract final account data from the advice map - let final_account_data = group_slice_elements::( - advice_map - .get(&final_account_hash.as_bytes()) - .ok_or(TransactionResultError::FinalAccountDataNotFound)?, - ); - let stub = parse_final_account_stub(final_account_data) - .map_err(TransactionResultError::FinalAccountStubDataInvalid)?; + // extract final account data from the advice map + let final_account_data = group_slice_elements::( + advice_map + .get(&final_account_hash.as_bytes()) + .ok_or(TransactionResultError::FinalAccountDataNotFound)?, + ); + let stub = parse_final_account_stub(final_account_data) + .map_err(TransactionResultError::FinalAccountStubDataInvalid)?; - Ok(Self(stub)) - } + Ok(stub) } diff --git a/miden-tx/src/tests.rs b/miden-tx/src/tests.rs index 9ef72fa77..91803193c 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, FinalAccountStub, InputNote, InputNotes, OutputNotes}, + transaction::{ChainMmr, InputNote, InputNotes, TransactionOutputs}, Felt, Word, }; use miden_prover::ProvingOptions; @@ -59,13 +59,12 @@ fn test_transaction_executor_witness() { let (advice_provider, _event_handler) = host.into_parts(); let (stack, map, store) = advice_provider.into_parts(); - let final_account_stub = - FinalAccountStub::try_from_vm_result(result.stack_outputs(), &stack, &map, &store).unwrap(); - let created_notes = - 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.output_notes(), &created_notes); + let tx_outputs = + TransactionOutputs::try_from_vm_result(result.stack_outputs(), &stack, &map, &store) + .unwrap(); + assert_eq!(transaction_result.final_account_hash(), tx_outputs.account.hash()); + assert_eq!(transaction_result.output_notes(), &tx_outputs.output_notes); } #[test] diff --git a/objects/src/accounts/stub.rs b/objects/src/accounts/stub.rs index e9c024262..41768c841 100644 --- a/objects/src/accounts/stub.rs +++ b/objects/src/accounts/stub.rs @@ -12,7 +12,7 @@ use super::{hash_account, Account, AccountId, Digest, Felt}; /// - vault_root: a commitment to the account's vault ([AccountVault]). /// - storage_root: accounts storage root ([AccountStorage]). /// - code_root: a commitment to the account's code ([AccountCode]). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct AccountStub { id: AccountId, diff --git a/objects/src/transaction/account_stub.rs b/objects/src/transaction/account_stub.rs deleted file mode 100644 index 8d12d5917..000000000 --- a/objects/src/transaction/account_stub.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::accounts::AccountStub; - -// FINAL ACCOUNT STUB -// ================================================================================================ -/// [FinalAccountStub] represents a stub of an account after a transaction has been executed. -pub struct FinalAccountStub(pub AccountStub); diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index 85c2218d8..466c8ee77 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -8,7 +8,6 @@ use super::{ StarkField, ToAdviceInputs, TransactionWitnessError, Word, WORD_SIZE, ZERO, }; -mod account_stub; mod chain_mmr; mod event; mod executed_tx; @@ -23,12 +22,11 @@ mod tx_witness; #[cfg(not(feature = "testing"))] mod utils; -pub use account_stub::FinalAccountStub; pub use chain_mmr::ChainMmr; pub use event::Event; pub use executed_tx::ExecutedTransaction; pub use inputs::{InputNote, InputNotes, TransactionInputs}; -pub use outputs::{OutputNote, OutputNotes}; +pub use outputs::{OutputNote, OutputNotes, TransactionOutputs}; 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 index cb5e2756b..72f118640 100644 --- a/objects/src/transaction/outputs.rs +++ b/objects/src/transaction/outputs.rs @@ -2,11 +2,22 @@ use core::cell::OnceCell; use super::MAX_OUTPUT_NOTES_PER_TRANSACTION; use crate::{ + accounts::AccountStub, notes::{Note, NoteEnvelope, NoteMetadata, NoteVault}, utils::collections::{self, BTreeSet, Vec}, Digest, Felt, Hasher, StarkField, TransactionResultError, Word, }; +// TRANSACTION OUTPUTS +// ================================================================================================ + +/// Describes the result of executing a transaction. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TransactionOutputs { + pub account: AccountStub, + pub output_notes: OutputNotes, +} + // OUTPUT NOTES // ================================================================================================ diff --git a/objects/src/transaction/tx_result.rs b/objects/src/transaction/tx_result.rs index 49e0686eb..46efd74db 100644 --- a/objects/src/transaction/tx_result.rs +++ b/objects/src/transaction/tx_result.rs @@ -3,7 +3,7 @@ use vm_processor::{AdviceInputs, Program}; use crate::{ accounts::{AccountDelta, AccountId}, transaction::{ - FinalAccountStub, InputNotes, OutputNotes, TransactionInputs, TransactionWitness, + InputNotes, OutputNotes, TransactionInputs, TransactionOutputs, TransactionWitness, }, Digest, TransactionResultError, }; @@ -44,9 +44,8 @@ impl TransactionResult { /// Creates a new [TransactionResult] from the provided data, advice provider and stack outputs. pub fn new( tx_inputs: TransactionInputs, - final_account_stub: FinalAccountStub, + tx_outputs: TransactionOutputs, account_delta: AccountDelta, - output_notes: OutputNotes, program: Program, tx_script_root: Option, advice_witness: AdviceInputs, @@ -54,10 +53,10 @@ impl TransactionResult { Ok(Self { account_id: tx_inputs.account.id(), initial_account_hash: tx_inputs.account.hash(), - final_account_hash: final_account_stub.0.hash(), + final_account_hash: tx_outputs.account.hash(), account_delta, input_notes: tx_inputs.input_notes, - output_notes, + output_notes: tx_outputs.output_notes, block_hash: tx_inputs.block_header.hash(), program, tx_script_root, From 8a748962f23cd31c135e0481356c61394bf5660d Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Dec 2023 13:54:16 -0800 Subject: [PATCH 07/21] chore: remove obsolete NoteStub file --- objects/src/notes/stub.rs | 90 --------------------------------------- 1 file changed, 90 deletions(-) delete mode 100644 objects/src/notes/stub.rs diff --git a/objects/src/notes/stub.rs b/objects/src/notes/stub.rs deleted file mode 100644 index 430d2758c..000000000 --- a/objects/src/notes/stub.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::{ - notes::{Note, NoteEnvelope, NoteMetadata, NoteVault}, - Digest, Hasher, NoteError, StarkField, -}; - -// NOTE STUB -// ================================================================================================ - -/// 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 NoteStub { - envelope: NoteEnvelope, - recipient: Digest, - vault: NoteVault, -} - -impl NoteStub { - // 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: NoteStub) -> Self { - note_stub.envelope - } -} - -impl From<&NoteStub> for NoteEnvelope { - fn from(note_stub: &NoteStub) -> Self { - note_stub.envelope - } -} - -impl From for NoteStub { - fn from(note: Note) -> Self { - (¬e).into() - } -} - -impl From<&Note> for NoteStub { - 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") - } -} From b3cd41642564ff9cda748398fb2095fd1eda6b51 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Dec 2023 20:46:36 -0800 Subject: [PATCH 08/21] refactor: integrate TransactionOutputs into ExecutedTransaction --- miden-lib/src/tests/test_account.rs | 8 +-- miden-lib/src/tests/test_epilogue.rs | 34 +++++----- miden-lib/src/tests/test_tx.rs | 6 +- mock/src/lib.rs | 5 +- mock/src/mock/transaction.rs | 18 +++-- mock/src/procedures.rs | 24 +++---- objects/src/transaction/executed_tx.rs | 92 +++++++++++--------------- objects/src/transaction/mod.rs | 2 +- objects/src/transaction/script.rs | 2 +- objects/src/transaction/utils.rs | 34 +--------- 10 files changed, 97 insertions(+), 128 deletions(-) diff --git a/miden-lib/src/tests/test_account.rs b/miden-lib/src/tests/test_account.rs index 9332278c5..8bac5f5d5 100644 --- a/miden-lib/src/tests/test_account.rs +++ b/miden-lib/src/tests/test_account.rs @@ -14,7 +14,7 @@ use mock::{ transaction::{mock_executed_tx, mock_inputs}, }, prepare_transaction, - procedures::{created_notes_data_procedure, prepare_word}, + procedures::{output_notes_data_procedure, prepare_word}, run_tx, run_within_tx_kernel, }; @@ -66,8 +66,8 @@ pub fn test_set_code_is_not_immediate() { pub fn test_set_code_succeeds() { let executed_transaction = mock_executed_tx(AssetPreservationStatus::Preserved); - let created_notes_data_procedure = - created_notes_data_procedure(executed_transaction.output_notes()); + let output_notes_data_procedure = + output_notes_data_procedure(executed_transaction.output_notes()); let code = format!( " @@ -75,7 +75,7 @@ pub fn test_set_code_succeeds() { use.miden::sat::internal::prologue use.miden::sat::internal::epilogue - {created_notes_data_procedure} + {output_notes_data_procedure} begin exec.prologue::prepare_transaction diff --git a/miden-lib/src/tests/test_epilogue.rs b/miden-lib/src/tests/test_epilogue.rs index 1d1263c04..7eda506a3 100644 --- a/miden-lib/src/tests/test_epilogue.rs +++ b/miden-lib/src/tests/test_epilogue.rs @@ -1,6 +1,6 @@ use mock::{ mock::{notes::AssetPreservationStatus, transaction::mock_executed_tx}, - procedures::created_notes_data_procedure, + procedures::output_notes_data_procedure, run_within_tx_kernel, }; @@ -20,13 +20,13 @@ const EPILOGUE_FILE: &str = "epilogue.masm"; fn test_epilogue() { let executed_transaction = mock_executed_tx(AssetPreservationStatus::Preserved); - let created_notes_data_procedure = - created_notes_data_procedure(executed_transaction.output_notes()); + let output_notes_data_procedure = + output_notes_data_procedure(executed_transaction.output_notes()); let imports = "use.miden::sat::internal::prologue\n"; let code = format!( " - {created_notes_data_procedure} + {output_notes_data_procedure} begin exec.prologue::prepare_transaction exec.create_mock_notes @@ -58,7 +58,7 @@ fn test_epilogue() { // assert created notes commitment is correct assert_eq!( process.stack.get_word(CREATED_NOTES_COMMITMENT_WORD_IDX), - executed_transaction.created_notes_commitment().as_elements() + executed_transaction.output_notes().commitment().as_elements() ); // assert final account hash is correct @@ -80,14 +80,14 @@ fn test_epilogue() { fn test_compute_created_note_hash() { let executed_transaction = mock_executed_tx(AssetPreservationStatus::Preserved); - let created_notes_data_procedure = - created_notes_data_procedure(executed_transaction.output_notes()); + let output_notes_data_procedure = + output_notes_data_procedure(executed_transaction.output_notes()); for (note, i) in executed_transaction.output_notes().iter().zip(0u32..) { let imports = "use.miden::sat::internal::prologue\n"; let test = format!( " - {created_notes_data_procedure} + {output_notes_data_procedure} begin exec.prologue::prepare_transaction exec.create_mock_notes @@ -131,13 +131,13 @@ fn test_epilogue_asset_preservation_violation() { ] { let executed_transaction = mock_executed_tx(asset_preservation); - let created_notes_data_procedure = - created_notes_data_procedure(executed_transaction.output_notes()); + let output_notes_data_procedure = + output_notes_data_procedure(executed_transaction.output_notes()); let imports = "use.miden::sat::internal::prologue\n"; let code = format!( " - {created_notes_data_procedure} + {output_notes_data_procedure} begin exec.prologue::prepare_transaction exec.create_mock_notes @@ -165,13 +165,13 @@ fn test_epilogue_asset_preservation_violation() { fn test_epilogue_increment_nonce_success() { let executed_transaction = mock_executed_tx(AssetPreservationStatus::Preserved); - let created_notes_data_procedure = - created_notes_data_procedure(executed_transaction.output_notes()); + let output_notes_data_procedure = + output_notes_data_procedure(executed_transaction.output_notes()); let imports = "use.miden::sat::internal::prologue\n"; let code = format!( " - {created_notes_data_procedure} + {output_notes_data_procedure} begin exec.prologue::prepare_transaction exec.create_mock_notes @@ -197,13 +197,13 @@ fn test_epilogue_increment_nonce_success() { fn test_epilogue_increment_nonce_violation() { let executed_transaction = mock_executed_tx(AssetPreservationStatus::Preserved); - let created_notes_data_procedure = - created_notes_data_procedure(executed_transaction.output_notes()); + let output_notes_data_procedure = + output_notes_data_procedure(executed_transaction.output_notes()); let imports = "use.miden::sat::internal::prologue\n"; let code = format!( " - {created_notes_data_procedure} + {output_notes_data_procedure} begin exec.prologue::prepare_transaction exec.create_mock_notes diff --git a/miden-lib/src/tests/test_tx.rs b/miden-lib/src/tests/test_tx.rs index f2e3ee438..6eba4fa10 100644 --- a/miden-lib/src/tests/test_tx.rs +++ b/miden-lib/src/tests/test_tx.rs @@ -1,4 +1,4 @@ -use miden_objects::{notes::Note, transaction::utils::generate_created_notes_commitment}; +use miden_objects::{notes::Note, transaction::OutputNotes}; use mock::{ constants::ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, mock::{account::MockAccountType, notes::AssetPreservationStatus, transaction::mock_inputs}, @@ -170,7 +170,9 @@ fn test_get_output_notes_hash() { // compute expected output notes hash let expected_output_notes_hash = - generate_created_notes_commitment(&[output_note_1.clone(), output_note_2.clone()]); + OutputNotes::new(vec![output_note_1.clone().into(), output_note_2.clone().into()]) + .unwrap() + .commitment(); let code = format!( " diff --git a/mock/src/lib.rs b/mock/src/lib.rs index 4eea167d5..66f582e87 100644 --- a/mock/src/lib.rs +++ b/mock/src/lib.rs @@ -3,9 +3,10 @@ use std::{fs::File, io::Read, path::PathBuf}; use miden_lib::{assembler::assembler, memory}; use miden_objects::{ accounts::Account, - notes::{Note, NoteVault}, + notes::NoteVault, transaction::{ - ChainMmr, InputNote, InputNotes, PreparedTransaction, TransactionInputs, TransactionScript, + ChainMmr, InputNote, InputNotes, OutputNotes, PreparedTransaction, TransactionInputs, + TransactionScript, }, BlockHeader, Felt, StarkField, }; diff --git a/mock/src/mock/transaction.rs b/mock/src/mock/transaction.rs index 8477b50f7..535e05e13 100644 --- a/mock/src/mock/transaction.rs +++ b/mock/src/mock/transaction.rs @@ -2,7 +2,10 @@ use miden_lib::assembler::assembler; use miden_objects::{ accounts::Account, notes::Note, - transaction::{ChainMmr, ExecutedTransaction, InputNote, InputNotes, TransactionInputs}, + transaction::{ + ChainMmr, ExecutedTransaction, InputNote, InputNotes, OutputNote, OutputNotes, + TransactionInputs, TransactionOutputs, + }, utils::collections::Vec, BlockHeader, Felt, FieldElement, }; @@ -106,10 +109,12 @@ pub fn mock_executed_tx(asset_preservation: AssetPreservationStatus) -> Executed mock_account(None, Felt::new(2), Some(initial_account.code().clone()), &assembler); // mock notes - let (consumed_notes, created_notes) = mock_notes(&assembler, &asset_preservation); + let (input_notes, output_notes) = mock_notes(&assembler, &asset_preservation); + + let output_notes = output_notes.into_iter().map(OutputNote::from).collect::>(); // Chain data - let (block_chain, input_notes) = mock_chain_data(consumed_notes); + let (block_chain, input_notes) = mock_chain_data(input_notes); // Block header let block_header = mock_block_header( @@ -127,6 +132,11 @@ pub fn mock_executed_tx(asset_preservation: AssetPreservationStatus) -> Executed input_notes: InputNotes::new(input_notes).unwrap(), }; + let tx_outputs = TransactionOutputs { + account: final_account.into(), + output_notes: OutputNotes::new(output_notes).unwrap(), + }; + // Executed Transaction - ExecutedTransaction::new(tx_inputs, final_account, created_notes, None).unwrap() + ExecutedTransaction::new(tx_inputs, tx_outputs, None).unwrap() } diff --git a/mock/src/procedures.rs b/mock/src/procedures.rs index f1863b7fb..9cf3c6a6a 100644 --- a/mock/src/procedures.rs +++ b/mock/src/procedures.rs @@ -3,21 +3,21 @@ use super::{ CREATED_NOTE_METADATA_OFFSET, CREATED_NOTE_RECIPIENT_OFFSET, CREATED_NOTE_SECTION_OFFSET, NOTE_MEM_SIZE, NUM_CREATED_NOTES_PTR, }, - Note, NoteVault, StarkField, Word, + NoteVault, OutputNotes, StarkField, Word, }; -pub fn created_notes_data_procedure(notes: &[Note]) -> String { - let note_0_metadata = prepare_word(¬es[0].metadata().into()); - let note_0_recipient = prepare_word(¬es[0].recipient()); - let note_0_assets = prepare_assets(notes[0].vault()); +pub fn output_notes_data_procedure(notes: &OutputNotes) -> String { + let note_0_metadata = prepare_word(¬es.get_note(0).metadata().into()); + let note_0_recipient = prepare_word(notes.get_note(0).recipient()); + let note_0_assets = prepare_assets(notes.get_note(0).vault()); - let note_1_metadata = prepare_word(¬es[1].metadata().into()); - let note_1_recipient = prepare_word(¬es[1].recipient()); - let note_1_assets = prepare_assets(notes[1].vault()); + let note_1_metadata = prepare_word(¬es.get_note(1).metadata().into()); + let note_1_recipient = prepare_word(notes.get_note(1).recipient()); + let note_1_assets = prepare_assets(notes.get_note(1).vault()); - let note_2_metadata = prepare_word(¬es[2].metadata().into()); - let note_2_recipient = prepare_word(¬es[2].recipient()); - let note_2_assets = prepare_assets(notes[2].vault()); + let note_2_metadata = prepare_word(¬es.get_note(2).metadata().into()); + let note_2_recipient = prepare_word(notes.get_note(2).recipient()); + let note_2_assets = prepare_assets(notes.get_note(2).vault()); const NOTE_1_OFFSET: u32 = NOTE_MEM_SIZE; const NOTE_2_OFFSET: u32 = NOTE_MEM_SIZE * 2; @@ -65,7 +65,7 @@ pub fn created_notes_data_procedure(notes: &[Note]) -> String { note_0_assets[0], note_1_assets[0], note_2_assets[0], - notes.len() + notes.num_notes() ) } diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index bc5df64d8..91a1b8e11 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -1,10 +1,12 @@ -use vm_core::StackOutputs; +use vm_core::StackInputs; -use super::{TransactionInputs, TransactionScript}; +use super::{ + utils, AdviceInputs, InputNotes, OutputNotes, TransactionInputs, TransactionOutputs, + TransactionScript, +}; use crate::{ - accounts::validate_account_seed, - transaction::{utils, Account, AdviceInputs, Digest, InputNotes, Note, StackInputs, Vec, Word}, - ExecutedTransactionError, + accounts::{validate_account_seed, Account, AccountStub}, + BlockHeader, ExecutedTransactionError, Word, }; // EXECUTED TRANSACTION @@ -13,36 +15,34 @@ use crate::{ #[derive(Debug)] pub struct ExecutedTransaction { tx_inputs: TransactionInputs, - final_account: Account, - created_notes: Vec, + tx_outputs: TransactionOutputs, tx_script: Option, } impl ExecutedTransaction { + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- /// Constructs a new [ExecutedTransaction] instance. pub fn new( tx_inputs: TransactionInputs, - final_account: Account, - created_notes: Vec, + tx_outputs: TransactionOutputs, tx_script: Option, ) -> Result { - Self::validate_new_account_seed(&tx_inputs.account, tx_inputs.account_seed)?; - Ok(Self { - tx_inputs, - final_account, - created_notes, - tx_script, - }) + validate_new_account_seed(&tx_inputs.account, tx_inputs.account_seed)?; + Ok(Self { tx_inputs, tx_outputs, tx_script }) } + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + /// Returns the initial account. pub fn initial_account(&self) -> &Account { &self.tx_inputs.account } /// Returns the final account. - pub fn final_account(&self) -> &Account { - &self.final_account + pub fn final_account(&self) -> &AccountStub { + &self.tx_outputs.account } /// Returns the consumed notes. @@ -51,18 +51,18 @@ impl ExecutedTransaction { } /// Returns the created notes. - pub fn output_notes(&self) -> &[Note] { - &self.created_notes + pub fn output_notes(&self) -> &OutputNotes { + &self.tx_outputs.output_notes } /// Returns a reference to the transaction script. - pub fn tx_script(&self) -> &Option { - &self.tx_script + pub fn tx_script(&self) -> Option<&TransactionScript> { + self.tx_script.as_ref() } - /// Returns the block hash. - pub fn block_hash(&self) -> Digest { - self.tx_inputs.block_header.hash() + /// Returns the block header. + pub fn block_header(&self) -> &BlockHeader { + &self.tx_inputs.block_header } /// Returns the stack inputs required when executing the transaction. @@ -70,39 +70,25 @@ impl ExecutedTransaction { utils::generate_stack_inputs(&self.tx_inputs) } - /// Returns the consumed notes commitment. - pub fn consumed_notes_commitment(&self) -> Digest { - self.input_notes().commitment() - } - /// Returns the advice inputs required when executing the transaction. pub fn advice_provider_inputs(&self) -> AdviceInputs { utils::generate_advice_provider_inputs(&self.tx_inputs, &self.tx_script) } +} - /// Returns the stack outputs produced as a result of executing a transaction. - pub fn stack_outputs(&self) -> StackOutputs { - utils::generate_stack_outputs(&self.created_notes, &self.final_account.hash()) - } - - /// Returns created notes commitment. - pub fn created_notes_commitment(&self) -> Digest { - utils::generate_created_notes_commitment(&self.created_notes) - } +// HELPER FUNCTIONS +// ================================================================================================ - // HELPERS - // -------------------------------------------------------------------------------------------- - /// Validates that a valid account seed has been provided if the account the transaction is - /// being executed against is new. - fn validate_new_account_seed( - account: &Account, - seed: Option, - ) -> Result<(), ExecutedTransactionError> { - match (account.is_new(), seed) { - (true, Some(seed)) => validate_account_seed(account, seed) - .map_err(ExecutedTransactionError::InvalidAccountIdSeedError), - (true, None) => Err(ExecutedTransactionError::AccountIdSeedNoteProvided), - _ => Ok(()), - } +/// Validates that a valid account seed has been provided if the account the transaction is +/// being executed against is new. +fn validate_new_account_seed( + account: &Account, + seed: Option, +) -> Result<(), ExecutedTransactionError> { + match (account.is_new(), seed) { + (true, Some(seed)) => validate_account_seed(account, seed) + .map_err(ExecutedTransactionError::InvalidAccountIdSeedError), + (true, None) => Err(ExecutedTransactionError::AccountIdSeedNoteProvided), + _ => Ok(()), } } diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index 466c8ee77..434002e82 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -1,4 +1,4 @@ -use vm_core::{Program, StackInputs, StackOutputs}; +use vm_core::{Program, StackInputs}; use super::{ accounts::{Account, AccountId}, diff --git a/objects/src/transaction/script.rs b/objects/src/transaction/script.rs index 5fc8465e9..6a8e8f7a8 100644 --- a/objects/src/transaction/script.rs +++ b/objects/src/transaction/script.rs @@ -91,7 +91,7 @@ impl TransactionScript { impl ToAdviceInputs for TransactionScript { fn to_advice_inputs(&self, target: &mut T) { // insert the transaction script hash into the advice stack - target.push_onto_stack(&**self.hash()); + target.push_onto_stack(self.hash().as_elements()); // insert map inputs into the advice map for (hash, input) in self.inputs.iter() { diff --git a/objects/src/transaction/utils.rs b/objects/src/transaction/utils.rs index 880f33501..de7ed9d77 100644 --- a/objects/src/transaction/utils.rs +++ b/objects/src/transaction/utils.rs @@ -1,8 +1,8 @@ use vm_core::utils::IntoBytes; use super::{ - AdviceInputs, Digest, Felt, Hasher, Note, StackInputs, StackOutputs, ToAdviceInputs, - TransactionInputs, TransactionScript, Vec, Word, ZERO, + AdviceInputs, Digest, Felt, StackInputs, ToAdviceInputs, TransactionInputs, TransactionScript, + Vec, Word, ZERO, }; // ADVICE INPUT CONSTRUCTORS @@ -114,33 +114,3 @@ pub fn generate_stack_inputs(tx_inputs: &TransactionInputs) -> StackInputs { inputs.extend_from_slice(tx_inputs.block_header.hash().as_elements()); StackInputs::new(inputs) } - -// OUTPUT STACK CONSTRUCTOR -// ================================================================================================ - -/// Returns the stack outputs produced as a result of executing a transaction. This includes the -/// final account hash and created notes commitment. -/// -/// Output: [CREATED_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH] -/// -/// - CREATED_NOTES_COMMITMENT is the commitment of the created notes -/// - FINAL_ACCOUNT_HASH is the final account hash -pub fn generate_stack_outputs(created_notes: &[Note], final_account_hash: &Digest) -> StackOutputs { - let mut outputs: Vec = Vec::with_capacity(8); - outputs.extend_from_slice(generate_created_notes_commitment(created_notes).as_elements()); - outputs.extend_from_slice(final_account_hash.as_elements()); - outputs.reverse(); - StackOutputs::from_elements(outputs, Default::default()).expect("stack outputs are valid") -} - -/// 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_commitment(notes: &[Note]) -> 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) -} From c8be761c7621b2f966d78f62c98e4ee0edf3ed44 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Dec 2023 22:19:02 -0800 Subject: [PATCH 09/21] refactor: replace TransactionResult with ExecutedTransaction --- miden-lib/src/transaction/account_stub.rs | 6 +- miden-tx/src/error.rs | 10 +- miden-tx/src/executor/mod.rs | 51 +++++---- miden-tx/src/lib.rs | 4 +- miden-tx/src/result.rs | 21 ++-- miden-tx/src/tests.rs | 2 +- miden-tx/tests/p2id_script_test.rs | 4 +- miden-tx/tests/p2idr_script_test.rs | 14 +-- miden-tx/tests/swap_script_test.rs | 2 +- miden-tx/tests/wallet_test.rs | 4 +- mock/src/mock/transaction.rs | 21 +++- objects/src/errors.rs | 69 ++++++----- objects/src/lib.rs | 4 +- objects/src/transaction/executed_tx.rs | 120 ++++++++++++++----- objects/src/transaction/inputs.rs | 25 +++- objects/src/transaction/mod.rs | 4 +- objects/src/transaction/outputs.rs | 8 +- objects/src/transaction/prepared_tx.rs | 28 ++--- objects/src/transaction/transaction_id.rs | 10 +- objects/src/transaction/tx_result.rs | 133 ---------------------- objects/src/transaction/tx_witness.rs | 1 - 21 files changed, 252 insertions(+), 289 deletions(-) delete mode 100644 objects/src/transaction/tx_result.rs diff --git a/miden-lib/src/transaction/account_stub.rs b/miden-lib/src/transaction/account_stub.rs index 21cdbcb98..4de3d60e4 100644 --- a/miden-lib/src/transaction/account_stub.rs +++ b/miden-lib/src/transaction/account_stub.rs @@ -1,7 +1,7 @@ use miden_objects::{ accounts::{Account, AccountId, AccountStorage, AccountStorageDelta, AccountStub}, crypto::merkle::{merkle_tree_delta, MerkleStore}, - AccountError, TransactionResultError, Word, + AccountError, ExecutedTransactionError, Word, }; use crate::memory::{ @@ -33,7 +33,7 @@ pub fn extract_account_storage_delta( store: &MerkleStore, initial_account: &Account, final_account_stub: &AccountStub, -) -> Result { +) -> Result { // extract storage slots delta let tree_delta = merkle_tree_delta( initial_account.storage().root(), @@ -41,7 +41,7 @@ pub fn extract_account_storage_delta( AccountStorage::STORAGE_TREE_DEPTH, store, ) - .map_err(TransactionResultError::ExtractAccountStorageSlotsDeltaFailed)?; + .map_err(ExecutedTransactionError::ExtractAccountStorageSlotsDeltaFailed)?; // map tree delta to cleared/updated slots; we can cast indexes to u8 because the // the number of storage slots cannot be greater than 256 diff --git a/miden-tx/src/error.rs b/miden-tx/src/error.rs index 416c64787..7b3c68b3d 100644 --- a/miden-tx/src/error.rs +++ b/miden-tx/src/error.rs @@ -1,12 +1,12 @@ use core::fmt; use miden_objects::{ - assembly::AssemblyError, crypto::merkle::NodeIndex, PreparedTransactionError, - TransactionWitnessError, + assembly::AssemblyError, crypto::merkle::NodeIndex, ExecutedTransactionError, + PreparedTransactionError, TransactionWitnessError, }; use miden_verifier::VerificationError; -use super::{AccountError, AccountId, Digest, ExecutionError, TransactionResultError}; +use super::{AccountError, AccountId, Digest, ExecutionError}; // TRANSACTION ERROR // ================================================================================================ @@ -62,7 +62,7 @@ pub enum TransactionExecutorError { FetchAccountCodeFailed(DataStoreError), FetchTransactionInputsFailed(DataStoreError), LoadAccountFailed(TransactionCompilerError), - TransactionResultError(TransactionResultError), + TransactionResultError(ExecutedTransactionError), } impl fmt::Display for TransactionExecutorError { @@ -79,7 +79,7 @@ impl std::error::Error for TransactionExecutorError {} #[derive(Debug)] pub enum TransactionProverError { ProveTransactionProgramFailed(ExecutionError), - TransactionResultError(TransactionResultError), + TransactionResultError(ExecutedTransactionError), CorruptTransactionWitnessConsumedNoteData(TransactionWitnessError), } diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index 835febd25..d5e1465b6 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -1,19 +1,22 @@ -use miden_lib::{outputs::TX_SCRIPT_ROOT_WORD_IDX, transaction::extract_account_storage_delta}; +use miden_lib::transaction::extract_account_storage_delta; use miden_objects::{ accounts::AccountDelta, assembly::ProgramAst, transaction::{TransactionInputs, TransactionOutputs, TransactionScript}, - Felt, TransactionResultError, Word, WORD_SIZE, + ExecutedTransactionError, Felt, Word, }; -use vm_core::{Program, StackOutputs, StarkField}; +use vm_core::{Program, StackOutputs}; use super::{ - AccountCode, AccountId, DataStore, Digest, NoteOrigin, NoteScript, PreparedTransaction, - RecAdviceProvider, ScriptTarget, TransactionCompiler, TransactionExecutorError, - TransactionHost, TransactionResult, + AccountCode, AccountId, DataStore, Digest, ExecutedTransaction, NoteOrigin, NoteScript, + PreparedTransaction, RecAdviceProvider, ScriptTarget, TransactionCompiler, + TransactionExecutorError, TransactionHost, }; use crate::{host::EventHandler, TryFromVmResult}; +// TRANSACTION EXECUTOR +// ================================================================================================ + /// The transaction executor is responsible for executing Miden rollup transactions. /// /// Transaction execution consists of the following steps: @@ -124,7 +127,7 @@ impl TransactionExecutor { block_ref: u32, note_origins: &[NoteOrigin], tx_script: Option, - ) -> Result { + ) -> Result { let transaction = self.prepare_transaction(account_id, block_ref, note_origins, tx_script)?; @@ -143,7 +146,7 @@ impl TransactionExecutor { let (advice_recorder, event_handler) = host.into_parts(); create_transaction_result( tx_program, - tx_script.map(|s| *s.hash()), + tx_script, tx_inputs, advice_recorder, result.stack_outputs().clone(), @@ -186,15 +189,15 @@ impl TransactionExecutor { } } -/// Creates a new [TransactionResult] from the provided data, advice provider and stack outputs. +/// Creates a new [ExecutedTransaction] from the provided data, advice provider and stack outputs. pub fn create_transaction_result( program: Program, - tx_script_root: Option, + tx_script: Option, tx_inputs: TransactionInputs, advice_provider: RecAdviceProvider, stack_outputs: StackOutputs, event_handler: EventHandler, -) -> Result { +) -> Result { // finalize the advice recorder let (advice_witness, stack, map, store) = advice_provider.finalize(); @@ -202,16 +205,16 @@ pub fn create_transaction_result( let tx_outputs = TransactionOutputs::try_from_vm_result(&stack_outputs, &stack, &map, &store)?; let final_account = &tx_outputs.account; - // assert the tx_script_root is consistent with the output stack - debug_assert_eq!( - (*tx_script_root.unwrap_or_default()) - .into_iter() - .rev() - .map(|x| x.as_int()) - .collect::>(), - stack_outputs.stack() - [TX_SCRIPT_ROOT_WORD_IDX * WORD_SIZE..(TX_SCRIPT_ROOT_WORD_IDX + 1) * WORD_SIZE] - ); + // TODO: assert the tx_script_root is consistent with the output stack + //debug_assert_eq!( + // (*tx_script_root.unwrap_or_default()) + // .into_iter() + // .rev() + // .map(|x| x.as_int()) + // .collect::>(), + // stack_outputs.stack() + // [TX_SCRIPT_ROOT_WORD_IDX * WORD_SIZE..(TX_SCRIPT_ROOT_WORD_IDX + 1) * WORD_SIZE] + //); let initial_account = &tx_inputs.account; @@ -233,12 +236,12 @@ pub fn create_transaction_result( let account_delta = AccountDelta::new(storage_delta, vault_delta, nonce_delta).expect("invalid account delta"); - TransactionResult::new( + ExecutedTransaction::new( + program, tx_inputs, tx_outputs, account_delta, - program, - tx_script_root, + tx_script, advice_witness, ) } diff --git a/miden-tx/src/lib.rs b/miden-tx/src/lib.rs index 7a274df74..9b356f3f9 100644 --- a/miden-tx/src/lib.rs +++ b/miden-tx/src/lib.rs @@ -4,9 +4,9 @@ use miden_objects::{ accounts::{AccountCode, AccountId}, assembly::CodeBlock, notes::{NoteOrigin, NoteScript}, - transaction::{PreparedTransaction, TransactionResult}, + transaction::{ExecutedTransaction, PreparedTransaction}, utils::collections::BTreeMap, - AccountError, Digest, Hasher, TransactionResultError, + AccountError, Digest, Hasher, }; use vm_core::Program; use vm_processor::{ExecutionError, RecAdviceProvider}; diff --git a/miden-tx/src/result.rs b/miden-tx/src/result.rs index 9d39a9ba4..0200af0e7 100644 --- a/miden-tx/src/result.rs +++ b/miden-tx/src/result.rs @@ -9,7 +9,7 @@ use miden_objects::{ crypto::merkle::MerkleStore, transaction::{OutputNote, OutputNotes, TransactionOutputs}, utils::collections::{BTreeMap, Vec}, - Digest, Felt, TransactionResultError, Word, WORD_SIZE, + Digest, ExecutedTransactionError, Felt, Word, WORD_SIZE, }; use vm_core::utils::group_slice_elements; use vm_processor::StackOutputs; @@ -28,7 +28,7 @@ pub trait TryFromVmResult: Sized { } impl TryFromVmResult for TransactionOutputs { - type Error = TransactionResultError; + type Error = ExecutedTransactionError; fn try_from_vm_result( stack_outputs: &StackOutputs, @@ -45,7 +45,7 @@ impl TryFromVmResult for TransactionOutputs { } impl TryFromVmResult for OutputNotes { - type Error = TransactionResultError; + type Error = ExecutedTransactionError; fn try_from_vm_result( stack_outputs: &StackOutputs, @@ -67,7 +67,7 @@ impl TryFromVmResult for OutputNotes { let created_notes_data = group_slice_elements::( advice_map .get(&created_notes_commitment.as_bytes()) - .ok_or(TransactionResultError::OutputNoteDataNotFound)?, + .ok_or(ExecutedTransactionError::OutputNoteDataNotFound)?, ); let mut created_notes = Vec::new(); @@ -75,14 +75,15 @@ impl TryFromVmResult for OutputNotes { while created_note_ptr < created_notes_data.len() { let note_stub: OutputNote = notes_try_from_elements(&created_notes_data[created_note_ptr..]) - .map_err(TransactionResultError::OutputNoteDataInvalid)?; + .map_err(ExecutedTransactionError::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).map_err(ExecutedTransactionError::OutputNotesError)?; if created_notes_commitment != created_notes.commitment() { - return Err(TransactionResultError::OutputNotesCommitmentInconsistent( + return Err(ExecutedTransactionError::OutputNotesCommitmentInconsistent( created_notes_commitment, created_notes.commitment(), )); @@ -98,7 +99,7 @@ impl TryFromVmResult for OutputNotes { fn extract_final_account_stub( stack_outputs: &StackOutputs, advice_map: &BTreeMap<[u8; 32], Vec>, -) -> Result { +) -> Result { let final_account_hash: Word = stack_outputs.stack() [FINAL_ACCOUNT_HASH_WORD_IDX * WORD_SIZE..(FINAL_ACCOUNT_HASH_WORD_IDX + 1) * WORD_SIZE] .iter() @@ -113,10 +114,10 @@ fn extract_final_account_stub( let final_account_data = group_slice_elements::( advice_map .get(&final_account_hash.as_bytes()) - .ok_or(TransactionResultError::FinalAccountDataNotFound)?, + .ok_or(ExecutedTransactionError::FinalAccountDataNotFound)?, ); let stub = parse_final_account_stub(final_account_data) - .map_err(TransactionResultError::FinalAccountStubDataInvalid)?; + .map_err(ExecutedTransactionError::FinalAccountStubDataInvalid)?; Ok(stub) } diff --git a/miden-tx/src/tests.rs b/miden-tx/src/tests.rs index 91803193c..49a71ec87 100644 --- a/miden-tx/src/tests.rs +++ b/miden-tx/src/tests.rs @@ -63,7 +63,7 @@ fn test_transaction_executor_witness() { let tx_outputs = TransactionOutputs::try_from_vm_result(result.stack_outputs(), &stack, &map, &store) .unwrap(); - assert_eq!(transaction_result.final_account_hash(), tx_outputs.account.hash()); + assert_eq!(transaction_result.final_account().hash(), tx_outputs.account.hash()); assert_eq!(transaction_result.output_notes(), &tx_outputs.output_notes); } diff --git a/miden-tx/tests/p2id_script_test.rs b/miden-tx/tests/p2id_script_test.rs index 4270b9338..d0c9b2b9d 100644 --- a/miden-tx/tests/p2id_script_test.rs +++ b/miden-tx/tests/p2id_script_test.rs @@ -90,7 +90,7 @@ fn test_p2id_script() { target_account.code().clone(), Felt::new(2), ); - assert_eq!(transaction_result.final_account_hash(), target_account_after.hash()); + assert_eq!(transaction_result.final_account().hash(), target_account_after.hash()); // CONSTRUCT AND EXECUTE TX (Failure) // -------------------------------------------------------------------------------------------- @@ -207,7 +207,7 @@ fn test_p2id_script_multiple_assets() { target_account.code().clone(), Felt::new(2), ); - assert_eq!(transaction_result.final_account_hash(), target_account_after.hash()); + assert_eq!(transaction_result.final_account().hash(), target_account_after.hash()); // CONSTRUCT AND EXECUTE TX (Failure) // -------------------------------------------------------------------------------------------- diff --git a/miden-tx/tests/p2idr_script_test.rs b/miden-tx/tests/p2idr_script_test.rs index 948aa6fd9..c65aa73eb 100644 --- a/miden-tx/tests/p2idr_script_test.rs +++ b/miden-tx/tests/p2idr_script_test.rs @@ -139,7 +139,7 @@ fn test_p2idr_script() { target_account.code().clone(), Felt::new(2), ); - assert_eq!(transaction_result_1.final_account_hash(), target_account_after.hash()); + assert_eq!(transaction_result_1.final_account().hash(), target_account_after.hash()); // CONSTRUCT AND EXECUTE TX (Case "in time" - Sender Account Execution Failure) // -------------------------------------------------------------------------------------------- @@ -170,7 +170,7 @@ fn test_p2idr_script() { Some(tx_script_sender.clone()), ); - // Check that we got the expected result - TransactionExecutorError and not TransactionResult + // Check that we got the expected result - TransactionExecutorError and not ExecutedTransaction // Second transaction should not work (sender consumes too early), we expect an error assert!(transaction_result_2.is_err()); @@ -203,7 +203,7 @@ fn test_p2idr_script() { Some(tx_script_malicious.clone()), ); - // Check that we got the expected result - TransactionExecutorError and not TransactionResult + // Check that we got the expected result - TransactionExecutorError and not ExecutedTransaction // Third transaction should not work (malicious account can never consume), we expect an error assert!(transaction_result_3.is_err()); @@ -231,7 +231,7 @@ fn test_p2idr_script() { ) .unwrap(); - // Check that we got the expected result - TransactionResult + // Check that we got the expected result - ExecutedTransaction // Assert that the target_account received the funds and the nonce increased by 1 // Nonce delta assert_eq!(transaction_result_4.account_delta().nonce(), Some(Felt::new(2))); @@ -244,7 +244,7 @@ fn test_p2idr_script() { target_account.code().clone(), Felt::new(2), ); - assert_eq!(transaction_result_4.final_account_hash(), target_account_after.hash()); + assert_eq!(transaction_result_4.final_account().hash(), target_account_after.hash()); // CONSTRUCT AND EXECUTE TX (Case "too late" - Execution Sender Account Success) // -------------------------------------------------------------------------------------------- @@ -278,7 +278,7 @@ fn test_p2idr_script() { sender_account.code().clone(), Felt::new(2), ); - assert_eq!(transaction_result_5.final_account_hash(), sender_account_after.hash()); + assert_eq!(transaction_result_5.final_account().hash(), sender_account_after.hash()); // CONSTRUCT AND EXECUTE TX (Case "too late" - Malicious Account Failure) // -------------------------------------------------------------------------------------------- @@ -303,7 +303,7 @@ fn test_p2idr_script() { Some(tx_script_malicious), ); - // Check that we got the expected result - TransactionExecutorError and not TransactionResult + // Check that we got the expected result - TransactionExecutorError and not ExecutedTransaction // Sixth transaction should not work (malicious account can never consume), we expect an error assert!(transaction_result_6.is_err()) } diff --git a/miden-tx/tests/swap_script_test.rs b/miden-tx/tests/swap_script_test.rs index b78bb5b69..760c7272e 100644 --- a/miden-tx/tests/swap_script_test.rs +++ b/miden-tx/tests/swap_script_test.rs @@ -100,7 +100,7 @@ fn test_swap_script() { ); // Check that the target account has received the asset from the note - assert_eq!(transaction_result.final_account_hash(), target_account_after.hash()); + assert_eq!(transaction_result.final_account().hash(), target_account_after.hash()); // Check if only one `Note` has been created assert_eq!(transaction_result.output_notes().num_notes(), 1); diff --git a/miden-tx/tests/wallet_test.rs b/miden-tx/tests/wallet_test.rs index da75818d3..0b813d957 100644 --- a/miden-tx/tests/wallet_test.rs +++ b/miden-tx/tests/wallet_test.rs @@ -105,7 +105,7 @@ fn test_receive_asset_via_wallet() { account_code, Felt::new(2), ); - assert_eq!(transaction_result.final_account_hash(), target_account_after.hash()); + assert_eq!(transaction_result.final_account().hash(), target_account_after.hash()); } #[test] @@ -184,7 +184,7 @@ fn test_send_asset_via_wallet() { sender_account_code, Felt::new(2), ); - assert_eq!(transaction_result.final_account_hash(), sender_account_after.hash()); + assert_eq!(transaction_result.final_account().hash(), sender_account_after.hash()); } #[cfg(not(target_arch = "wasm32"))] diff --git a/mock/src/mock/transaction.rs b/mock/src/mock/transaction.rs index 535e05e13..7762930f0 100644 --- a/mock/src/mock/transaction.rs +++ b/mock/src/mock/transaction.rs @@ -1,6 +1,6 @@ use miden_lib::assembler::assembler; use miden_objects::{ - accounts::Account, + accounts::{Account, AccountDelta}, notes::Note, transaction::{ ChainMmr, ExecutedTransaction, InputNote, InputNotes, OutputNote, OutputNotes, @@ -9,7 +9,7 @@ use miden_objects::{ utils::collections::Vec, BlockHeader, Felt, FieldElement, }; -use vm_processor::AdviceInputs; +use vm_processor::{AdviceInputs, Operation, Program}; use super::{ account::{ @@ -137,6 +137,21 @@ pub fn mock_executed_tx(asset_preservation: AssetPreservationStatus) -> Executed output_notes: OutputNotes::new(output_notes).unwrap(), }; + // dummy components + let program = build_dummy_tx_program(); + let account_delta = AccountDelta::default(); + let advice_witness = AdviceInputs::default(); + // Executed Transaction - ExecutedTransaction::new(tx_inputs, tx_outputs, None).unwrap() + ExecutedTransaction::new(program, tx_inputs, tx_outputs, account_delta, None, advice_witness) + .unwrap() +} + +// HELPER FUNCTIONS +// ================================================================================================ + +fn build_dummy_tx_program() -> Program { + let operations = vec![Operation::Push(Felt::ZERO), Operation::Drop]; + let span = miden_objects::assembly::CodeBlock::new_span(operations); + Program::new(span) } diff --git a/objects/src/errors.rs b/objects/src/errors.rs index 13dfa38bb..1e16e67ce 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -274,7 +274,10 @@ impl std::error::Error for ChainMmrError {} #[derive(Debug, Clone)] pub enum TransactionInputsError { + AccountSeedNoteProvidedForNewAccount, + AccountSeedProvidedForExistingAccount, DuplicateInputNote(Digest), + InvalidAccountSeed(AccountError), TooManyInputNotes { max: usize, actual: usize }, } @@ -287,6 +290,24 @@ impl fmt::Display for TransactionInputsError { #[cfg(feature = "std")] impl std::error::Error for TransactionInputsError {} +// TRANSACTION OUTPUTS ERROR +// ================================================================================================ + +#[derive(Debug, Clone)] +pub enum TransactionOutputsError { + DuplicateOutputNote(Digest), + TooManyOutputNotes { max: usize, actual: usize }, +} + +impl fmt::Display for TransactionOutputsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TransactionOutputsError {} + // TRANSACTION SCRIPT ERROR // ================================================================================================ @@ -310,10 +331,9 @@ impl std::error::Error for TransactionScriptError {} // PREPARED TRANSACTION ERROR // =============================================================================================== -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum PreparedTransactionError { - InvalidAccountIdSeedError(AccountError), - AccountIdSeedNoteProvided, + AccountSeedError(TransactionInputsError), } impl fmt::Display for PreparedTransactionError { @@ -327,47 +347,34 @@ impl std::error::Error for PreparedTransactionError {} // EXECUTED TRANSACTION ERROR // =============================================================================================== -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ExecutedTransactionError { - InvalidAccountIdSeedError(AccountError), - AccountIdSeedNoteProvided, -} - -impl fmt::Display for ExecutedTransactionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ExecutedTransactionError {} - -// TRANSACTION RESULT ERROR -// ================================================================================================ -#[derive(Debug)] -pub enum TransactionResultError { - OutputNoteDataNotFound, - OutputNoteDataInvalid(NoteError), - OutputNotesCommitmentInconsistent(Digest, Digest), - DuplicateOutputNote(Digest), - FinalAccountDataNotFound, - FinalAccountStubDataInvalid(AccountError), - InconsistentAccountCodeHash(Digest, Digest), + AccountSeedError(TransactionInputsError), ExtractAccountStorageSlotsDeltaFailed(MerkleError), ExtractAccountStorageStoreDeltaFailed(MerkleError), ExtractAccountVaultLeavesDeltaFailed(MerkleError), - TooManyOutputNotes { max: usize, actual: usize }, + FinalAccountDataNotFound, + FinalAccountStubDataInvalid(AccountError), + InconsistentAccountCodeHash(Digest, Digest), + InconsistentAccountId { + input_id: AccountId, + output_id: AccountId, + }, + OutputNoteDataNotFound, + OutputNoteDataInvalid(NoteError), + OutputNotesCommitmentInconsistent(Digest, Digest), + OutputNotesError(TransactionOutputsError), UpdatedAccountCodeInvalid(AccountError), } -impl fmt::Display for TransactionResultError { +impl fmt::Display for ExecutedTransactionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } } #[cfg(feature = "std")] -impl std::error::Error for TransactionResultError {} +impl std::error::Error for ExecutedTransactionError {} // TRANSACTION WITNESS ERROR // ================================================================================================ diff --git a/objects/src/lib.rs b/objects/src/lib.rs index fadfed368..ad8f0e6ea 100644 --- a/objects/src/lib.rs +++ b/objects/src/lib.rs @@ -21,7 +21,7 @@ pub mod transaction; mod errors; pub use errors::{ AccountDeltaError, AccountError, AssetError, ChainMmrError, ExecutedTransactionError, - NoteError, PreparedTransactionError, TransactionInputsError, TransactionResultError, + NoteError, PreparedTransactionError, TransactionInputsError, TransactionOutputsError, TransactionScriptError, TransactionWitnessError, }; // RE-EXPORTS @@ -34,7 +34,7 @@ pub mod assembly { ast::{AstSerdeOptions, ModuleAst, ProgramAst}, Assembler, AssemblyContext, AssemblyError, }; - pub use vm_core::code_blocks::CodeBlock; + pub use vm_core::{code_blocks::CodeBlock, Program}; } pub mod crypto { diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index 91a1b8e11..f61f32646 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -1,56 +1,118 @@ use vm_core::StackInputs; use super::{ - utils, AdviceInputs, InputNotes, OutputNotes, TransactionInputs, TransactionOutputs, - TransactionScript, + utils, AdviceInputs, InputNotes, OutputNotes, TransactionId, TransactionInputs, + TransactionOutputs, TransactionScript, TransactionWitness, }; use crate::{ - accounts::{validate_account_seed, Account, AccountStub}, - BlockHeader, ExecutedTransactionError, Word, + accounts::{Account, AccountDelta, AccountId, AccountStub}, + assembly::Program, + BlockHeader, ExecutedTransactionError, }; // EXECUTED TRANSACTION // ================================================================================================ -#[derive(Debug)] +/// Describes the result of executing a transaction program. +/// +/// Executed transaction contains the following data: +/// - +#[derive(Debug, Clone)] pub struct ExecutedTransaction { + id: TransactionId, + program: Program, tx_inputs: TransactionInputs, tx_outputs: TransactionOutputs, + account_delta: AccountDelta, tx_script: Option, + advice_witness: AdviceInputs, } impl ExecutedTransaction { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- - /// Constructs a new [ExecutedTransaction] instance. + + /// Returns a new [ExecutedTransaction] instantiated from the provided data. + /// + /// # Errors + /// Returns an error if: + /// - Input and output account IDs are not the same. + /// - For a new account, account seed is not provided or the provided seed is invalid. + /// - For an existing account, account seed was provided. pub fn new( + program: Program, tx_inputs: TransactionInputs, tx_outputs: TransactionOutputs, + account_delta: AccountDelta, tx_script: Option, + advice_witness: AdviceInputs, ) -> Result { - validate_new_account_seed(&tx_inputs.account, tx_inputs.account_seed)?; - Ok(Self { tx_inputs, tx_outputs, tx_script }) + // make sure account IDs are consistent across transaction inputs and outputs + if tx_inputs.account.id() != tx_inputs.account.id() { + return Err(ExecutedTransactionError::InconsistentAccountId { + input_id: tx_inputs.account.id(), + output_id: tx_outputs.account.id(), + }); + } + + // if this transaction was executed against a new account, validate the account seed + tx_inputs + .validate_new_account_seed() + .map_err(ExecutedTransactionError::AccountSeedError)?; + + // build transaction ID + let id = TransactionId::new( + tx_inputs.account.hash(), + tx_outputs.account.hash(), + tx_inputs.input_notes.commitment(), + tx_outputs.output_notes.commitment(), + ); + + Ok(Self { + id, + program, + tx_inputs, + tx_outputs, + account_delta, + tx_script, + advice_witness, + }) } // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns the initial account. + /// Returns a unique identifier of this transaction. + pub fn id(&self) -> TransactionId { + self.id + } + + /// Returns a reference the program defining this transaction. + pub fn program(&self) -> &Program { + &self.program + } + + /// Returns the ID of the account against which this transaction was executed. + pub fn account_id(&self) -> AccountId { + self.initial_account().id() + } + + /// Returns the description of the account before the transaction was executed. pub fn initial_account(&self) -> &Account { &self.tx_inputs.account } - /// Returns the final account. + /// Returns description of the account after the transaction was executed. pub fn final_account(&self) -> &AccountStub { &self.tx_outputs.account } - /// Returns the consumed notes. + /// Returns the notes consumed in this transaction. pub fn input_notes(&self) -> &InputNotes { &self.tx_inputs.input_notes } - /// Returns the created notes. + /// Returns the notes created in this transaction. pub fn output_notes(&self) -> &OutputNotes { &self.tx_outputs.output_notes } @@ -60,11 +122,16 @@ impl ExecutedTransaction { self.tx_script.as_ref() } - /// Returns the block header. + /// Returns the block header for the block against which the transaction was executed. pub fn block_header(&self) -> &BlockHeader { &self.tx_inputs.block_header } + /// Returns a description of changes between the initial and final account states. + pub fn account_delta(&self) -> &AccountDelta { + &self.account_delta + } + /// Returns the stack inputs required when executing the transaction. pub fn stack_inputs(&self) -> StackInputs { utils::generate_stack_inputs(&self.tx_inputs) @@ -74,21 +141,20 @@ impl ExecutedTransaction { pub fn advice_provider_inputs(&self) -> AdviceInputs { utils::generate_advice_provider_inputs(&self.tx_inputs, &self.tx_script) } -} -// HELPER FUNCTIONS -// ================================================================================================ + // CONVERSIONS + // -------------------------------------------------------------------------------------------- -/// Validates that a valid account seed has been provided if the account the transaction is -/// being executed against is new. -fn validate_new_account_seed( - account: &Account, - seed: Option, -) -> Result<(), ExecutedTransactionError> { - match (account.is_new(), seed) { - (true, Some(seed)) => validate_account_seed(account, seed) - .map_err(ExecutedTransactionError::InvalidAccountIdSeedError), - (true, None) => Err(ExecutedTransactionError::AccountIdSeedNoteProvided), - _ => Ok(()), + /// Converts this transaction into a [TransactionWitness]. + pub fn into_witness(self) -> TransactionWitness { + TransactionWitness::new( + self.initial_account().id(), + self.initial_account().hash(), + self.block_header().hash(), + self.input_notes().commitment(), + self.tx_script().map(|s| *s.hash()), + self.program, + self.advice_witness, + ) } } diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index f8f531df9..043b88968 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -1,11 +1,12 @@ use core::cell::OnceCell; use super::{ - Account, AdviceInputsBuilder, BlockHeader, ChainMmr, Digest, Felt, Hasher, Note, Nullifier, - ToAdviceInputs, Word, MAX_INPUT_NOTES_PER_TRANSACTION, + AdviceInputsBuilder, BlockHeader, ChainMmr, Digest, Felt, Hasher, ToAdviceInputs, Word, + MAX_INPUT_NOTES_PER_TRANSACTION, }; use crate::{ - notes::{NoteInclusionProof, NoteOrigin}, + accounts::{validate_account_seed, Account}, + notes::{Note, NoteInclusionProof, NoteOrigin, Nullifier}, utils::{ collections::{self, BTreeSet, Vec}, serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, @@ -27,6 +28,24 @@ pub struct TransactionInputs { pub input_notes: InputNotes, } +impl TransactionInputs { + /// Validates that a valid account seed has been provided for new accounts. + /// + /// # Errors + /// Returns an error if: + /// - For a new account, account seed is not provided or the provided seed is invalid. + /// - For an existing account, account seed was provided. + pub fn validate_new_account_seed(&self) -> Result<(), TransactionInputsError> { + match (self.account.is_new(), self.account_seed) { + (true, Some(seed)) => validate_account_seed(&self.account, seed) + .map_err(TransactionInputsError::InvalidAccountSeed), + (true, None) => Err(TransactionInputsError::AccountSeedNoteProvidedForNewAccount), + (false, Some(_)) => Err(TransactionInputsError::AccountSeedProvidedForExistingAccount), + (false, None) => Ok(()), + } + } +} + // INPUT NOTES // ================================================================================================ diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index 434002e82..85a23cca9 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -2,7 +2,7 @@ use vm_core::{Program, StackInputs}; use super::{ accounts::{Account, AccountId}, - notes::{Note, NoteEnvelope, Nullifier}, + notes::{NoteEnvelope, Nullifier}, utils::collections::Vec, AdviceInputs, AdviceInputsBuilder, BlockHeader, Digest, Felt, Hasher, PreparedTransactionError, StarkField, ToAdviceInputs, TransactionWitnessError, Word, WORD_SIZE, ZERO, @@ -17,7 +17,6 @@ mod prepared_tx; mod proven_tx; mod script; mod transaction_id; -mod tx_result; mod tx_witness; #[cfg(not(feature = "testing"))] mod utils; @@ -31,7 +30,6 @@ pub use prepared_tx::PreparedTransaction; pub use proven_tx::ProvenTransaction; pub use script::TransactionScript; pub use transaction_id::TransactionId; -pub use tx_result::TransactionResult; pub use tx_witness::TransactionWitness; #[cfg(feature = "testing")] diff --git a/objects/src/transaction/outputs.rs b/objects/src/transaction/outputs.rs index 72f118640..9d136423d 100644 --- a/objects/src/transaction/outputs.rs +++ b/objects/src/transaction/outputs.rs @@ -5,7 +5,7 @@ use crate::{ accounts::AccountStub, notes::{Note, NoteEnvelope, NoteMetadata, NoteVault}, utils::collections::{self, BTreeSet, Vec}, - Digest, Felt, Hasher, StarkField, TransactionResultError, Word, + Digest, Felt, Hasher, StarkField, TransactionOutputsError, Word, }; // TRANSACTION OUTPUTS @@ -37,9 +37,9 @@ impl OutputNotes { /// 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 { + pub fn new(notes: Vec) -> Result { if notes.len() > MAX_OUTPUT_NOTES_PER_TRANSACTION { - return Err(TransactionResultError::TooManyOutputNotes { + return Err(TransactionOutputsError::TooManyOutputNotes { max: MAX_OUTPUT_NOTES_PER_TRANSACTION, actual: notes.len(), }); @@ -48,7 +48,7 @@ impl OutputNotes { let mut seen_notes = BTreeSet::new(); for note in notes.iter() { if !seen_notes.insert(note.hash()) { - return Err(TransactionResultError::DuplicateOutputNote(note.hash())); + return Err(TransactionOutputsError::DuplicateOutputNote(note.hash())); } } diff --git a/objects/src/transaction/prepared_tx.rs b/objects/src/transaction/prepared_tx.rs index 6eb35e18f..007f319ac 100644 --- a/objects/src/transaction/prepared_tx.rs +++ b/objects/src/transaction/prepared_tx.rs @@ -1,8 +1,7 @@ use super::{ utils, Account, AdviceInputs, BlockHeader, ChainMmr, InputNotes, PreparedTransactionError, - Program, StackInputs, TransactionInputs, TransactionScript, Word, + Program, StackInputs, TransactionInputs, TransactionScript, }; -use crate::accounts::validate_account_seed; // PREPARED TRANSACTION // ================================================================================================ @@ -25,12 +24,18 @@ impl PreparedTransaction { // -------------------------------------------------------------------------------------------- /// Returns a new [PreparedTransaction] instantiated from the provided executable transaction /// program and inputs required to execute this program. + /// + /// # Returns an error if: + /// - For a new account, account seed is not provided or the provided seed is invalid. + /// - For an existing account, account seed was provided. pub fn new( program: Program, tx_script: Option, tx_inputs: TransactionInputs, ) -> Result { - validate_new_account_seed(&tx_inputs.account, tx_inputs.account_seed)?; + tx_inputs + .validate_new_account_seed() + .map_err(PreparedTransactionError::AccountSeedError)?; Ok(Self { program, tx_script, tx_inputs }) } @@ -85,20 +90,3 @@ impl PreparedTransaction { (self.program, self.tx_script, self.tx_inputs) } } - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Validates that a valid account seed has been provided if the account the transaction is -/// being executed against is new. -fn validate_new_account_seed( - account: &Account, - seed: Option, -) -> Result<(), PreparedTransactionError> { - match (account.is_new(), seed) { - (true, Some(seed)) => validate_account_seed(account, seed) - .map_err(PreparedTransactionError::InvalidAccountIdSeedError), - (true, None) => Err(PreparedTransactionError::AccountIdSeedNoteProvided), - _ => Ok(()), - } -} diff --git a/objects/src/transaction/transaction_id.rs b/objects/src/transaction/transaction_id.rs index 215431aab..4511e0033 100644 --- a/objects/src/transaction/transaction_id.rs +++ b/objects/src/transaction/transaction_id.rs @@ -1,5 +1,5 @@ use super::{ - Digest, Felt, Hasher, ProvenTransaction, TransactionResult, Vec, Word, WORD_SIZE, ZERO, + Digest, ExecutedTransaction, Felt, Hasher, ProvenTransaction, Vec, Word, WORD_SIZE, ZERO, }; use crate::utils::serde::{ ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, @@ -78,13 +78,13 @@ impl From<&ProvenTransaction> for TransactionId { } } -impl From<&TransactionResult> for TransactionId { - fn from(tx: &TransactionResult) -> Self { +impl From<&ExecutedTransaction> for TransactionId { + fn from(tx: &ExecutedTransaction) -> Self { 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(), + tx.initial_account().hash(), + tx.final_account().hash(), input_notes_hash, output_notes_hash, ) diff --git a/objects/src/transaction/tx_result.rs b/objects/src/transaction/tx_result.rs deleted file mode 100644 index 46efd74db..000000000 --- a/objects/src/transaction/tx_result.rs +++ /dev/null @@ -1,133 +0,0 @@ -use vm_processor::{AdviceInputs, Program}; - -use crate::{ - accounts::{AccountDelta, AccountId}, - transaction::{ - InputNotes, OutputNotes, TransactionInputs, TransactionOutputs, TransactionWitness, - }, - Digest, TransactionResultError, -}; - -// TRANSACTION RESULT -// ================================================================================================ - -/// [TransactionResult] represents the result of the execution of the transaction kernel. -/// -/// [TransactionResult] is a container for the following data: -/// - account_id: the ID of the account against which the transaction was executed. -/// - initial_account_hash: the initial account hash. -/// - final_account_hash: the final account hash. -/// - account_delta: a delta between the initial and final accounts. -/// - consumed_notes: the notes consumed by the transaction. -/// - created_notes: the notes created by the transaction. -/// - block_hash: the hash of the block against which the transaction was executed. -/// - program: the program that was executed. -/// - tx_script_root: the script root of the transaction. -/// - advice_witness: an advice witness that contains the minimum required data to execute a tx. -#[derive(Debug, Clone)] -pub struct TransactionResult { - account_id: AccountId, - initial_account_hash: Digest, - final_account_hash: Digest, - account_delta: AccountDelta, - input_notes: InputNotes, - output_notes: OutputNotes, - block_hash: Digest, - program: Program, - tx_script_root: Option, - advice_witness: AdviceInputs, -} - -impl TransactionResult { - // CONSTRUCTORS - // -------------------------------------------------------------------------------------------- - /// Creates a new [TransactionResult] from the provided data, advice provider and stack outputs. - pub fn new( - tx_inputs: TransactionInputs, - tx_outputs: TransactionOutputs, - account_delta: AccountDelta, - program: Program, - tx_script_root: Option, - advice_witness: AdviceInputs, - ) -> Result { - Ok(Self { - account_id: tx_inputs.account.id(), - initial_account_hash: tx_inputs.account.hash(), - final_account_hash: tx_outputs.account.hash(), - account_delta, - input_notes: tx_inputs.input_notes, - output_notes: tx_outputs.output_notes, - block_hash: tx_inputs.block_header.hash(), - program, - tx_script_root, - advice_witness, - }) - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns the ID of the account for which this transaction was executed. - pub fn account_id(&self) -> AccountId { - self.account_id - } - - /// Returns a reference to the initial account hash. - pub fn initial_account_hash(&self) -> Digest { - self.initial_account_hash - } - - /// Returns a reference to the final account hash. - pub fn final_account_hash(&self) -> Digest { - self.final_account_hash - } - - /// Returns a reference to the account delta. - pub fn account_delta(&self) -> &AccountDelta { - &self.account_delta - } - - /// Returns a reference to the consumed notes. - pub fn input_notes(&self) -> &InputNotes { - &self.input_notes - } - - /// Returns a reference to the created notes. - pub fn output_notes(&self) -> &OutputNotes { - &self.output_notes - } - - /// Returns the block hash the transaction was executed against. - pub fn block_hash(&self) -> Digest { - self.block_hash - } - - /// Returns a reference the transaction program. - pub fn program(&self) -> &Program { - &self.program - } - - /// Returns the root of the transaction script. - pub fn tx_script_root(&self) -> Option { - self.tx_script_root - } - - /// Returns a reference to the advice provider. - pub fn advice_witness(&self) -> &AdviceInputs { - &self.advice_witness - } - - // CONSUMERS - // -------------------------------------------------------------------------------------------- - pub fn into_witness(self) -> TransactionWitness { - TransactionWitness::new( - self.account_id, - self.initial_account_hash, - self.block_hash, - 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 b2630be5f..0423fc0f0 100644 --- a/objects/src/transaction/tx_witness.rs +++ b/objects/src/transaction/tx_witness.rs @@ -27,7 +27,6 @@ pub struct TransactionWitness { impl TransactionWitness { /// Creates a new [TransactionWitness] from the provided data. - #[allow(clippy::too_many_arguments)] pub fn new( account_id: AccountId, initial_account_hash: Digest, From 7a68d49c4c0daff48dc2a1d7d5967f80c322d53a Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 26 Dec 2023 22:46:46 -0800 Subject: [PATCH 10/21] refactor: consolidate transaction errors --- miden-lib/src/transaction/account_stub.rs | 6 +- miden-tx/src/compiler/mod.rs | 5 +- miden-tx/src/error.rs | 12 ++- miden-tx/src/executor/mod.rs | 65 +++++++------ miden-tx/src/result.rs | 20 ++-- objects/src/errors.rs | 92 +++++-------------- objects/src/lib.rs | 5 +- objects/src/transaction/executed_tx.rs | 22 +++-- objects/src/transaction/inputs.rs | 16 ++-- objects/src/transaction/mod.rs | 8 +- objects/src/transaction/outputs.rs | 8 +- objects/src/transaction/prepared_tx.rs | 11 +-- objects/src/transaction/proven_tx.rs | 3 + .../transaction/{script.rs => tx_script.rs} | 8 +- objects/src/transaction/tx_witness.rs | 3 + 15 files changed, 130 insertions(+), 154 deletions(-) rename objects/src/transaction/{script.rs => tx_script.rs} (93%) diff --git a/miden-lib/src/transaction/account_stub.rs b/miden-lib/src/transaction/account_stub.rs index 4de3d60e4..21cdbcb98 100644 --- a/miden-lib/src/transaction/account_stub.rs +++ b/miden-lib/src/transaction/account_stub.rs @@ -1,7 +1,7 @@ use miden_objects::{ accounts::{Account, AccountId, AccountStorage, AccountStorageDelta, AccountStub}, crypto::merkle::{merkle_tree_delta, MerkleStore}, - AccountError, ExecutedTransactionError, Word, + AccountError, TransactionResultError, Word, }; use crate::memory::{ @@ -33,7 +33,7 @@ pub fn extract_account_storage_delta( store: &MerkleStore, initial_account: &Account, final_account_stub: &AccountStub, -) -> Result { +) -> Result { // extract storage slots delta let tree_delta = merkle_tree_delta( initial_account.storage().root(), @@ -41,7 +41,7 @@ pub fn extract_account_storage_delta( AccountStorage::STORAGE_TREE_DEPTH, store, ) - .map_err(ExecutedTransactionError::ExtractAccountStorageSlotsDeltaFailed)?; + .map_err(TransactionResultError::ExtractAccountStorageSlotsDeltaFailed)?; // map tree delta to cleared/updated slots; we can cast indexes to u8 because the // the number of storage slots cannot be greater than 256 diff --git a/miden-tx/src/compiler/mod.rs b/miden-tx/src/compiler/mod.rs index 735b988fd..0592e9f8a 100644 --- a/miden-tx/src/compiler/mod.rs +++ b/miden-tx/src/compiler/mod.rs @@ -1,7 +1,7 @@ use miden_objects::{ assembly::{Assembler, AssemblyContext, ModuleAst, ProgramAst}, transaction::{InputNotes, TransactionScript}, - Felt, TransactionScriptError, Word, + Felt, TransactionError, Word, }; use vm_processor::ProgramInfo; @@ -112,9 +112,10 @@ impl TransactionCompiler { let (tx_script, code_block) = TransactionScript::new(tx_script_ast, tx_script_inputs, &mut self.assembler).map_err( |e| match e { - TransactionScriptError::ScriptCompilationError(asm_error) => { + TransactionError::ScriptCompilationError(asm_error) => { TransactionCompilerError::CompileTxScriptFailed(asm_error) }, + _ => TransactionCompilerError::CompileTxScriptFailedUnknown, }, )?; for target in target_account_proc.into_iter() { diff --git a/miden-tx/src/error.rs b/miden-tx/src/error.rs index 7b3c68b3d..1cebaf68b 100644 --- a/miden-tx/src/error.rs +++ b/miden-tx/src/error.rs @@ -1,8 +1,8 @@ use core::fmt; use miden_objects::{ - assembly::AssemblyError, crypto::merkle::NodeIndex, ExecutedTransactionError, - PreparedTransactionError, TransactionWitnessError, + assembly::AssemblyError, crypto::merkle::NodeIndex, TransactionResultError, + TransactionWitnessError, }; use miden_verifier::VerificationError; @@ -38,6 +38,7 @@ pub enum TransactionCompilerError { TxScriptIncompatibleWithAccountInterface(Digest), CompileNoteScriptFailed, CompileTxScriptFailed(AssemblyError), + CompileTxScriptFailedUnknown, BuildCodeBlockTableFailed(AssemblyError), } @@ -57,12 +58,13 @@ pub enum TransactionExecutorError { CompileNoteScriptFailed(TransactionCompilerError), CompileTransactionScriptFailed(TransactionCompilerError), CompileTransactionError(TransactionCompilerError), - ConstructPreparedTransactionFailed(PreparedTransactionError), + ConstructPreparedTransactionFailed(miden_objects::TransactionError), ExecuteTransactionProgramFailed(ExecutionError), + ExecutedTransactionConstructionFailed(miden_objects::TransactionError), FetchAccountCodeFailed(DataStoreError), FetchTransactionInputsFailed(DataStoreError), LoadAccountFailed(TransactionCompilerError), - TransactionResultError(ExecutedTransactionError), + TransactionResultError(TransactionResultError), } impl fmt::Display for TransactionExecutorError { @@ -79,7 +81,7 @@ impl std::error::Error for TransactionExecutorError {} #[derive(Debug)] pub enum TransactionProverError { ProveTransactionProgramFailed(ExecutionError), - TransactionResultError(ExecutedTransactionError), + TransactionResultError(TransactionResultError), CorruptTransactionWitnessConsumedNoteData(TransactionWitnessError), } diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index d5e1465b6..055e94e2f 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -1,11 +1,11 @@ -use miden_lib::transaction::extract_account_storage_delta; +use miden_lib::{outputs::TX_SCRIPT_ROOT_WORD_IDX, transaction::extract_account_storage_delta}; use miden_objects::{ accounts::AccountDelta, assembly::ProgramAst, transaction::{TransactionInputs, TransactionOutputs, TransactionScript}, - ExecutedTransactionError, Felt, Word, + Felt, Word, WORD_SIZE, }; -use vm_core::{Program, StackOutputs}; +use vm_core::{Program, StackOutputs, StarkField}; use super::{ AccountCode, AccountId, DataStore, Digest, ExecutedTransaction, NoteOrigin, NoteScript, @@ -21,14 +21,14 @@ use crate::{host::EventHandler, TryFromVmResult}; /// /// Transaction execution consists of the following steps: /// - Fetch the data required to execute a transaction from the [DataStore]. -/// - Compile the transaction into a program using the [TransactionComplier]. -/// - Execute the transaction program and create a [TransactionWitness]. +/// - Compile the transaction into a program using the [TransactionComplier](crate::TransactionCompiler). +/// - Execute the transaction program and create an [ExecutedTransaction]. /// /// The [TransactionExecutor] is generic over the [DataStore] which allows it to be used with /// different data backend implementations. /// /// The [TransactionExecutor::execute_transaction()] method is the main entry point for the -/// executor and produces a [TransactionWitness] for the transaction. The TransactionWitness can +/// executor and produces a [ExecutedTransaction] for the transaction. The executed transaction can /// then be used to by the prover to generate a proof transaction execution. pub struct TransactionExecutor { compiler: TransactionCompiler, @@ -44,7 +44,7 @@ impl TransactionExecutor { Self { compiler, data_store } } - // MODIFIERS + // STATE MUTATORS // -------------------------------------------------------------------------------------------- /// Fetches the account code from the [DataStore], compiles it, and loads the compiled code @@ -109,12 +109,15 @@ impl TransactionExecutor { .map_err(TransactionExecutorError::CompileTransactionScriptFailed) } - /// Prepares and executes a transaction specified by the provided arguments and returns a - /// [TransactionWitness]. + // TRANSACTION EXECUTION + // -------------------------------------------------------------------------------------------- + + /// Prepares and executes a transaction specified by the provided arguments and returns an + /// [ExecutedTransaction]. /// /// The method first fetches the data required to execute the transaction from the [DataStore] /// and compile the transaction into an executable program. Then it executes the transaction - /// program and creates a [TransactionWitness]. + /// program and creates an [ExecutedTransaction] object. /// /// # Errors: /// Returns an error if: @@ -144,7 +147,7 @@ impl TransactionExecutor { let (tx_program, tx_script, tx_inputs) = transaction.into_parts(); let (advice_recorder, event_handler) = host.into_parts(); - create_transaction_result( + build_executed_transaction( tx_program, tx_script, tx_inputs, @@ -152,9 +155,11 @@ impl TransactionExecutor { result.stack_outputs().clone(), event_handler, ) - .map_err(TransactionExecutorError::TransactionResultError) } + // HELPER METHODS + // -------------------------------------------------------------------------------------------- + /// Fetches the data required to execute the transaction from the [DataStore], compiles the /// transaction into an executable program using the [TransactionComplier], and returns a /// [PreparedTransaction]. @@ -163,7 +168,7 @@ impl TransactionExecutor { /// Returns an error if: /// - If required data can not be fetched from the [DataStore]. /// - If the transaction can not be compiled. - pub fn prepare_transaction( + pub(crate) fn prepare_transaction( &mut self, account_id: AccountId, block_ref: u32, @@ -189,38 +194,43 @@ impl TransactionExecutor { } } +// HELPER FUNCTIONS +// ================================================================================================ + /// Creates a new [ExecutedTransaction] from the provided data, advice provider and stack outputs. -pub fn create_transaction_result( +pub fn build_executed_transaction( program: Program, tx_script: Option, tx_inputs: TransactionInputs, advice_provider: RecAdviceProvider, stack_outputs: StackOutputs, event_handler: EventHandler, -) -> Result { +) -> Result { // finalize the advice recorder let (advice_witness, stack, map, store) = advice_provider.finalize(); // parse transaction results - let tx_outputs = TransactionOutputs::try_from_vm_result(&stack_outputs, &stack, &map, &store)?; + let tx_outputs = TransactionOutputs::try_from_vm_result(&stack_outputs, &stack, &map, &store) + .map_err(TransactionExecutorError::TransactionResultError)?; let final_account = &tx_outputs.account; - // TODO: assert the tx_script_root is consistent with the output stack - //debug_assert_eq!( - // (*tx_script_root.unwrap_or_default()) - // .into_iter() - // .rev() - // .map(|x| x.as_int()) - // .collect::>(), - // stack_outputs.stack() - // [TX_SCRIPT_ROOT_WORD_IDX * WORD_SIZE..(TX_SCRIPT_ROOT_WORD_IDX + 1) * WORD_SIZE] - //); + // assert the tx_script_root is consistent with the output stack + debug_assert_eq!( + (*tx_script.clone().map(|s| *s.hash()).unwrap_or_default()) + .into_iter() + .rev() + .map(|x| x.as_int()) + .collect::>(), + stack_outputs.stack() + [TX_SCRIPT_ROOT_WORD_IDX * WORD_SIZE..(TX_SCRIPT_ROOT_WORD_IDX + 1) * WORD_SIZE] + ); let initial_account = &tx_inputs.account; // TODO: Fix delta extraction for new account creation // extract the account storage delta - let storage_delta = extract_account_storage_delta(&store, initial_account, final_account)?; + let storage_delta = extract_account_storage_delta(&store, initial_account, final_account) + .map_err(TransactionExecutorError::TransactionResultError)?; // extract the nonce delta let nonce_delta = if initial_account.nonce() != final_account.nonce() { @@ -244,4 +254,5 @@ pub fn create_transaction_result( tx_script, advice_witness, ) + .map_err(TransactionExecutorError::ExecutedTransactionConstructionFailed) } diff --git a/miden-tx/src/result.rs b/miden-tx/src/result.rs index 0200af0e7..861461edf 100644 --- a/miden-tx/src/result.rs +++ b/miden-tx/src/result.rs @@ -9,7 +9,7 @@ use miden_objects::{ crypto::merkle::MerkleStore, transaction::{OutputNote, OutputNotes, TransactionOutputs}, utils::collections::{BTreeMap, Vec}, - Digest, ExecutedTransactionError, Felt, Word, WORD_SIZE, + Digest, Felt, TransactionResultError, Word, WORD_SIZE, }; use vm_core::utils::group_slice_elements; use vm_processor::StackOutputs; @@ -28,7 +28,7 @@ pub trait TryFromVmResult: Sized { } impl TryFromVmResult for TransactionOutputs { - type Error = ExecutedTransactionError; + type Error = TransactionResultError; fn try_from_vm_result( stack_outputs: &StackOutputs, @@ -45,7 +45,7 @@ impl TryFromVmResult for TransactionOutputs { } impl TryFromVmResult for OutputNotes { - type Error = ExecutedTransactionError; + type Error = TransactionResultError; fn try_from_vm_result( stack_outputs: &StackOutputs, @@ -67,7 +67,7 @@ impl TryFromVmResult for OutputNotes { let created_notes_data = group_slice_elements::( advice_map .get(&created_notes_commitment.as_bytes()) - .ok_or(ExecutedTransactionError::OutputNoteDataNotFound)?, + .ok_or(TransactionResultError::OutputNoteDataNotFound)?, ); let mut created_notes = Vec::new(); @@ -75,15 +75,15 @@ impl TryFromVmResult for OutputNotes { while created_note_ptr < created_notes_data.len() { let note_stub: OutputNote = notes_try_from_elements(&created_notes_data[created_note_ptr..]) - .map_err(ExecutedTransactionError::OutputNoteDataInvalid)?; + .map_err(TransactionResultError::OutputNoteDataInvalid)?; created_notes.push(note_stub); created_note_ptr += NOTE_MEM_SIZE as usize; } let created_notes = - Self::new(created_notes).map_err(ExecutedTransactionError::OutputNotesError)?; + Self::new(created_notes).map_err(TransactionResultError::OutputNotesError)?; if created_notes_commitment != created_notes.commitment() { - return Err(ExecutedTransactionError::OutputNotesCommitmentInconsistent( + return Err(TransactionResultError::OutputNotesCommitmentInconsistent( created_notes_commitment, created_notes.commitment(), )); @@ -99,7 +99,7 @@ impl TryFromVmResult for OutputNotes { fn extract_final_account_stub( stack_outputs: &StackOutputs, advice_map: &BTreeMap<[u8; 32], Vec>, -) -> Result { +) -> Result { let final_account_hash: Word = stack_outputs.stack() [FINAL_ACCOUNT_HASH_WORD_IDX * WORD_SIZE..(FINAL_ACCOUNT_HASH_WORD_IDX + 1) * WORD_SIZE] .iter() @@ -114,10 +114,10 @@ fn extract_final_account_stub( let final_account_data = group_slice_elements::( advice_map .get(&final_account_hash.as_bytes()) - .ok_or(ExecutedTransactionError::FinalAccountDataNotFound)?, + .ok_or(TransactionResultError::FinalAccountDataNotFound)?, ); let stub = parse_final_account_stub(final_account_data) - .map_err(ExecutedTransactionError::FinalAccountStubDataInvalid)?; + .map_err(TransactionResultError::FinalAccountStubDataInvalid)?; Ok(stub) } diff --git a/objects/src/errors.rs b/objects/src/errors.rs index 1e16e67ce..e5c46769b 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -269,112 +269,66 @@ impl fmt::Display for ChainMmrError { #[cfg(feature = "std")] impl std::error::Error for ChainMmrError {} -// TRANSACTION INPUTS ERROR +// TRANSACTION ERROR // ================================================================================================ #[derive(Debug, Clone)] -pub enum TransactionInputsError { +pub enum TransactionError { AccountSeedNoteProvidedForNewAccount, AccountSeedProvidedForExistingAccount, DuplicateInputNote(Digest), - InvalidAccountSeed(AccountError), - TooManyInputNotes { max: usize, actual: usize }, -} - -impl fmt::Display for TransactionInputsError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for TransactionInputsError {} - -// TRANSACTION OUTPUTS ERROR -// ================================================================================================ - -#[derive(Debug, Clone)] -pub enum TransactionOutputsError { DuplicateOutputNote(Digest), - TooManyOutputNotes { max: usize, actual: usize }, -} - -impl fmt::Display for TransactionOutputsError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for TransactionOutputsError {} - -// TRANSACTION SCRIPT ERROR -// ================================================================================================ - -#[derive(Debug, Clone)] -pub enum TransactionScriptError { + InconsistentAccountId { + input_id: AccountId, + output_id: AccountId, + }, + InvalidAccountSeed(AccountError), ScriptCompilationError(AssemblyError), + TooManyInputNotes { + max: usize, + actual: usize, + }, + TooManyOutputNotes { + max: usize, + actual: usize, + }, } -impl fmt::Display for TransactionScriptError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ScriptCompilationError(err) => { - write!(f, "transaction script compilation error: {}", err) - }, - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for TransactionScriptError {} - -// PREPARED TRANSACTION ERROR -// =============================================================================================== -#[derive(Debug, Clone)] -pub enum PreparedTransactionError { - AccountSeedError(TransactionInputsError), -} - -impl fmt::Display for PreparedTransactionError { +impl fmt::Display for TransactionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } } #[cfg(feature = "std")] -impl std::error::Error for PreparedTransactionError {} +impl std::error::Error for TransactionError {} -// EXECUTED TRANSACTION ERROR +// TRANSACTION RESULT ERROR // =============================================================================================== + #[derive(Debug, Clone)] -pub enum ExecutedTransactionError { - AccountSeedError(TransactionInputsError), +pub enum TransactionResultError { ExtractAccountStorageSlotsDeltaFailed(MerkleError), ExtractAccountStorageStoreDeltaFailed(MerkleError), ExtractAccountVaultLeavesDeltaFailed(MerkleError), FinalAccountDataNotFound, FinalAccountStubDataInvalid(AccountError), InconsistentAccountCodeHash(Digest, Digest), - InconsistentAccountId { - input_id: AccountId, - output_id: AccountId, - }, OutputNoteDataNotFound, OutputNoteDataInvalid(NoteError), OutputNotesCommitmentInconsistent(Digest, Digest), - OutputNotesError(TransactionOutputsError), + OutputNotesError(TransactionError), UpdatedAccountCodeInvalid(AccountError), } -impl fmt::Display for ExecutedTransactionError { +impl fmt::Display for TransactionResultError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } } #[cfg(feature = "std")] -impl std::error::Error for ExecutedTransactionError {} +impl std::error::Error for TransactionResultError {} // TRANSACTION WITNESS ERROR // ================================================================================================ diff --git a/objects/src/lib.rs b/objects/src/lib.rs index ad8f0e6ea..2bf477c55 100644 --- a/objects/src/lib.rs +++ b/objects/src/lib.rs @@ -20,9 +20,8 @@ pub mod transaction; mod errors; pub use errors::{ - AccountDeltaError, AccountError, AssetError, ChainMmrError, ExecutedTransactionError, - NoteError, PreparedTransactionError, TransactionInputsError, TransactionOutputsError, - TransactionScriptError, TransactionWitnessError, + AccountDeltaError, AccountError, AssetError, ChainMmrError, NoteError, TransactionError, + TransactionResultError, TransactionWitnessError, }; // RE-EXPORTS // ================================================================================================ diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index f61f32646..35378959f 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -7,16 +7,22 @@ use super::{ use crate::{ accounts::{Account, AccountDelta, AccountId, AccountStub}, assembly::Program, - BlockHeader, ExecutedTransactionError, + BlockHeader, TransactionError, }; // EXECUTED TRANSACTION // ================================================================================================ -/// Describes the result of executing a transaction program. +/// Describes the result of executing a transaction program for the Miden rollup. /// -/// Executed transaction contains the following data: -/// - +/// Executed transaction serves two primary purposes: +/// - It contains a complete description of the effects of the transaction. Specifically, it +/// contains all output notes created as the result of the transaction and describes all the +/// changes make to the involved account (i.e., the account delta). +/// - It contains all the information required to re-execute and prove the transaction in a +/// stateless manner. This includes all public transaction inputs, but also all nondeterministic +/// inputs that the host provided to Miden VM while executing the transaction (i.e., advice +/// witness). #[derive(Debug, Clone)] pub struct ExecutedTransaction { id: TransactionId, @@ -46,19 +52,17 @@ impl ExecutedTransaction { account_delta: AccountDelta, tx_script: Option, advice_witness: AdviceInputs, - ) -> Result { + ) -> Result { // make sure account IDs are consistent across transaction inputs and outputs if tx_inputs.account.id() != tx_inputs.account.id() { - return Err(ExecutedTransactionError::InconsistentAccountId { + return Err(TransactionError::InconsistentAccountId { input_id: tx_inputs.account.id(), output_id: tx_outputs.account.id(), }); } // if this transaction was executed against a new account, validate the account seed - tx_inputs - .validate_new_account_seed() - .map_err(ExecutedTransactionError::AccountSeedError)?; + tx_inputs.validate_new_account_seed()?; // build transaction ID let id = TransactionId::new( diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index 043b88968..9556db206 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -12,7 +12,7 @@ use crate::{ serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, string::ToString, }, - TransactionInputsError, + TransactionError, }; // TRANSACTION INPUTS @@ -35,12 +35,12 @@ impl TransactionInputs { /// Returns an error if: /// - For a new account, account seed is not provided or the provided seed is invalid. /// - For an existing account, account seed was provided. - pub fn validate_new_account_seed(&self) -> Result<(), TransactionInputsError> { + pub fn validate_new_account_seed(&self) -> Result<(), TransactionError> { match (self.account.is_new(), self.account_seed) { (true, Some(seed)) => validate_account_seed(&self.account, seed) - .map_err(TransactionInputsError::InvalidAccountSeed), - (true, None) => Err(TransactionInputsError::AccountSeedNoteProvidedForNewAccount), - (false, Some(_)) => Err(TransactionInputsError::AccountSeedProvidedForExistingAccount), + .map_err(TransactionError::InvalidAccountSeed), + (true, None) => Err(TransactionError::AccountSeedNoteProvidedForNewAccount), + (false, Some(_)) => Err(TransactionError::AccountSeedProvidedForExistingAccount), (false, None) => Ok(()), } } @@ -67,9 +67,9 @@ impl InputNotes { /// 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 { + pub fn new(notes: Vec) -> Result { if notes.len() > MAX_INPUT_NOTES_PER_TRANSACTION { - return Err(TransactionInputsError::TooManyInputNotes { + return Err(TransactionError::TooManyInputNotes { max: MAX_INPUT_NOTES_PER_TRANSACTION, actual: notes.len(), }); @@ -78,7 +78,7 @@ impl InputNotes { let mut seen_notes = BTreeSet::new(); for note in notes.iter() { if !seen_notes.insert(note.note().hash()) { - return Err(TransactionInputsError::DuplicateInputNote(note.note().hash())); + return Err(TransactionError::DuplicateInputNote(note.note().hash())); } } diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index 85a23cca9..0956d29cc 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -4,8 +4,8 @@ use super::{ accounts::{Account, AccountId}, notes::{NoteEnvelope, Nullifier}, utils::collections::Vec, - AdviceInputs, AdviceInputsBuilder, BlockHeader, Digest, Felt, Hasher, PreparedTransactionError, - StarkField, ToAdviceInputs, TransactionWitnessError, Word, WORD_SIZE, ZERO, + AdviceInputs, AdviceInputsBuilder, BlockHeader, Digest, Felt, Hasher, StarkField, + ToAdviceInputs, TransactionWitnessError, Word, WORD_SIZE, ZERO, }; mod chain_mmr; @@ -15,8 +15,8 @@ mod inputs; mod outputs; mod prepared_tx; mod proven_tx; -mod script; mod transaction_id; +mod tx_script; mod tx_witness; #[cfg(not(feature = "testing"))] mod utils; @@ -28,8 +28,8 @@ pub use inputs::{InputNote, InputNotes, TransactionInputs}; pub use outputs::{OutputNote, OutputNotes, TransactionOutputs}; pub use prepared_tx::PreparedTransaction; pub use proven_tx::ProvenTransaction; -pub use script::TransactionScript; pub use transaction_id::TransactionId; +pub use tx_script::TransactionScript; pub use tx_witness::TransactionWitness; #[cfg(feature = "testing")] diff --git a/objects/src/transaction/outputs.rs b/objects/src/transaction/outputs.rs index 9d136423d..b028aea16 100644 --- a/objects/src/transaction/outputs.rs +++ b/objects/src/transaction/outputs.rs @@ -5,7 +5,7 @@ use crate::{ accounts::AccountStub, notes::{Note, NoteEnvelope, NoteMetadata, NoteVault}, utils::collections::{self, BTreeSet, Vec}, - Digest, Felt, Hasher, StarkField, TransactionOutputsError, Word, + Digest, Felt, Hasher, StarkField, TransactionError, Word, }; // TRANSACTION OUTPUTS @@ -37,9 +37,9 @@ impl OutputNotes { /// 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 { + pub fn new(notes: Vec) -> Result { if notes.len() > MAX_OUTPUT_NOTES_PER_TRANSACTION { - return Err(TransactionOutputsError::TooManyOutputNotes { + return Err(TransactionError::TooManyOutputNotes { max: MAX_OUTPUT_NOTES_PER_TRANSACTION, actual: notes.len(), }); @@ -48,7 +48,7 @@ impl OutputNotes { let mut seen_notes = BTreeSet::new(); for note in notes.iter() { if !seen_notes.insert(note.hash()) { - return Err(TransactionOutputsError::DuplicateOutputNote(note.hash())); + return Err(TransactionError::DuplicateOutputNote(note.hash())); } } diff --git a/objects/src/transaction/prepared_tx.rs b/objects/src/transaction/prepared_tx.rs index 007f319ac..4024d116a 100644 --- a/objects/src/transaction/prepared_tx.rs +++ b/objects/src/transaction/prepared_tx.rs @@ -1,7 +1,8 @@ use super::{ - utils, Account, AdviceInputs, BlockHeader, ChainMmr, InputNotes, PreparedTransactionError, - Program, StackInputs, TransactionInputs, TransactionScript, + utils, Account, AdviceInputs, BlockHeader, ChainMmr, InputNotes, Program, StackInputs, + TransactionInputs, TransactionScript, }; +use crate::TransactionError; // PREPARED TRANSACTION // ================================================================================================ @@ -32,10 +33,8 @@ impl PreparedTransaction { program: Program, tx_script: Option, tx_inputs: TransactionInputs, - ) -> Result { - tx_inputs - .validate_new_account_seed() - .map_err(PreparedTransactionError::AccountSeedError)?; + ) -> Result { + tx_inputs.validate_new_account_seed()?; Ok(Self { program, tx_script, tx_inputs }) } diff --git a/objects/src/transaction/proven_tx.rs b/objects/src/transaction/proven_tx.rs index 4f4a942f7..6f1ff10d4 100644 --- a/objects/src/transaction/proven_tx.rs +++ b/objects/src/transaction/proven_tx.rs @@ -4,6 +4,9 @@ use vm_processor::DeserializationError; use super::{AccountId, Digest, NoteEnvelope, Nullifier, TransactionId, Vec}; +// PROVEN TRANSACTION +// ================================================================================================ + /// Resultant object of executing and proving a transaction. It contains the minimal /// amount of data needed to verify that the transaction was executed correctly. /// Contains: diff --git a/objects/src/transaction/script.rs b/objects/src/transaction/tx_script.rs similarity index 93% rename from objects/src/transaction/script.rs rename to objects/src/transaction/tx_script.rs index 6a8e8f7a8..52bb04a22 100644 --- a/objects/src/transaction/script.rs +++ b/objects/src/transaction/tx_script.rs @@ -2,7 +2,7 @@ use super::{Digest, Felt, Word}; use crate::{ advice::{AdviceInputsBuilder, ToAdviceInputs}, assembly::{Assembler, AssemblyContext, CodeBlock, ProgramAst}, - errors::TransactionScriptError, + errors::TransactionError, utils::collections::{BTreeMap, Vec}, }; @@ -39,10 +39,10 @@ impl TransactionScript { code: ProgramAst, inputs: T, assembler: &mut Assembler, - ) -> Result<(Self, CodeBlock), TransactionScriptError> { + ) -> Result<(Self, CodeBlock), TransactionError> { let code_block = assembler .compile_in_context(&code, &mut AssemblyContext::for_program(Some(&code))) - .map_err(TransactionScriptError::ScriptCompilationError)?; + .map_err(TransactionError::ScriptCompilationError)?; Ok(( Self { code, @@ -61,7 +61,7 @@ impl TransactionScript { code: ProgramAst, hash: Digest, inputs: T, - ) -> Result { + ) -> Result { Ok(Self { code, hash, diff --git a/objects/src/transaction/tx_witness.rs b/objects/src/transaction/tx_witness.rs index 0423fc0f0..3a8d53385 100644 --- a/objects/src/transaction/tx_witness.rs +++ b/objects/src/transaction/tx_witness.rs @@ -3,6 +3,9 @@ use super::{ TransactionWitnessError, Vec, Word, WORD_SIZE, }; +// TRANSACTION WITNESS +// ================================================================================================ + /// A [TransactionWitness] is the minimum required data required to execute and prove a Miden rollup /// transaction. /// From 950bb1e3738f46c7dfb108417328cadf6345d10f Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Wed, 27 Dec 2023 13:04:12 -0800 Subject: [PATCH 11/21] refactor: move tx input construction to miden-lib --- miden-lib/src/tests/mod.rs | 11 +- miden-lib/src/tests/test_account.rs | 70 ++++-------- miden-lib/src/tests/test_asset.rs | 15 +-- miden-lib/src/tests/test_asset_vault.rs | 106 +++++------------ miden-lib/src/tests/test_epilogue.rs | 26 +++-- miden-lib/src/tests/test_faucet.rs | 98 ++++++---------- miden-lib/src/tests/test_note.rs | 62 +++------- miden-lib/src/tests/test_prologue.rs | 70 ++++-------- miden-lib/src/tests/test_tx.rs | 22 ++-- .../src/transaction/inputs.rs | 107 ++++++++++++------ miden-lib/src/transaction/mod.rs | 3 + miden-tx/src/executor/mod.rs | 22 +++- miden-tx/src/prover/mod.rs | 16 ++- objects/src/transaction/executed_tx.rs | 17 +-- objects/src/transaction/mod.rs | 5 - objects/src/transaction/prepared_tx.rs | 20 ++-- 16 files changed, 263 insertions(+), 407 deletions(-) rename objects/src/transaction/utils.rs => miden-lib/src/transaction/inputs.rs (68%) diff --git a/miden-lib/src/tests/mod.rs b/miden-lib/src/tests/mod.rs index b359d04c3..5605ad0ea 100644 --- a/miden-lib/src/tests/mod.rs +++ b/miden-lib/src/tests/mod.rs @@ -1,11 +1,12 @@ use std::path::PathBuf; -use vm_core::{crypto::hash::Rpo256 as Hasher, Felt, StackInputs, Word, ONE, ZERO}; +use miden_objects::transaction::PreparedTransaction; +use vm_core::{crypto::hash::Rpo256 as Hasher, Felt, Program, StackInputs, Word, ONE, ZERO}; use vm_processor::{ AdviceProvider, ContextId, DefaultHost, MemAdviceProvider, Process, ProcessState, }; -use super::Library; +use super::{transaction::ToTransactionKernelInputs, Library}; mod test_account; mod test_asset; @@ -42,6 +43,12 @@ fn test_compile() { // HELPER FUNCTIONS // ================================================================================================ +fn build_tx_inputs(tx: &PreparedTransaction) -> (Program, StackInputs, MemAdviceProvider) { + let (stack_inputs, advice_inputs) = tx.get_kernel_inputs(); + let advice_provider = MemAdviceProvider::from(advice_inputs); + (tx.program().clone(), stack_inputs, advice_provider) +} + fn build_module_path(dir: &str, file: &str) -> PathBuf { [env!("CARGO_MANIFEST_DIR"), "asm", dir, file].iter().collect() } diff --git a/miden-lib/src/tests/test_account.rs b/miden-lib/src/tests/test_account.rs index 8bac5f5d5..8d798cf80 100644 --- a/miden-lib/src/tests/test_account.rs +++ b/miden-lib/src/tests/test_account.rs @@ -18,7 +18,10 @@ use mock::{ run_tx, run_within_tx_kernel, }; -use super::{ContextId, Felt, MemAdviceProvider, ProcessState, StackInputs, Word, ONE, ZERO}; +use super::{ + super::transaction::ToTransactionKernelInputs, build_tx_inputs, ContextId, Felt, + MemAdviceProvider, ProcessState, StackInputs, Word, ONE, ZERO, +}; use crate::memory::{ACCT_CODE_ROOT_PTR, ACCT_NEW_CODE_ROOT_PTR}; // ACCOUNT CODE TESTS @@ -41,13 +44,8 @@ pub fn test_set_code_is_not_immediate() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); // assert the code root is not changed assert_eq!( @@ -92,14 +90,10 @@ pub fn test_set_code_succeeds() { " ); - let process = run_within_tx_kernel( - "", - &code, - executed_transaction.stack_inputs(), - MemAdviceProvider::from(executed_transaction.advice_provider_inputs()), - None, - ) - .unwrap(); + let (stack_inputs, advice_inputs) = executed_transaction.get_kernel_inputs(); + let process = + run_within_tx_kernel("", &code, stack_inputs, MemAdviceProvider::from(advice_inputs), None) + .unwrap(); // assert the code root is changed after the epilogue assert_eq!( @@ -259,13 +253,8 @@ fn test_get_item() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let _process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } } @@ -319,13 +308,8 @@ fn test_set_item() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let _process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } // TODO: reenable once storage map support is implemented @@ -374,13 +358,8 @@ fn test_get_map_item() { "", None, ); - - let _process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } // ACCOUNT VAULT TESTS @@ -411,13 +390,8 @@ fn test_get_vault_commitment() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let _process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } // PROCEDURE AUTHENTICATION TESTS @@ -459,12 +433,8 @@ fn test_authenticate_procedure() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); match valid { true => assert!(process.is_ok()), diff --git a/miden-lib/src/tests/test_asset.rs b/miden-lib/src/tests/test_asset.rs index 9e349d7c1..d5403211b 100644 --- a/miden-lib/src/tests/test_asset.rs +++ b/miden-lib/src/tests/test_asset.rs @@ -9,7 +9,7 @@ use mock::{ run_tx, }; -use super::{Hasher, MemAdviceProvider, Word, ONE}; +use super::{build_tx_inputs, Hasher, Word, ONE}; #[test] fn test_create_fungible_asset_succeeds() { @@ -46,10 +46,8 @@ fn test_create_fungible_asset_succeeds() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider); } #[test] @@ -88,9 +86,6 @@ fn test_create_non_fungible_asset_succeeds() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } diff --git a/miden-lib/src/tests/test_asset_vault.rs b/miden-lib/src/tests/test_asset_vault.rs index 4c30892d8..30c0b221f 100644 --- a/miden-lib/src/tests/test_asset_vault.rs +++ b/miden-lib/src/tests/test_asset_vault.rs @@ -14,7 +14,7 @@ use mock::{ run_tx, }; -use super::{ContextId, Felt, MemAdviceProvider, ProcessState, Word, ONE, ZERO}; +use super::{build_tx_inputs, ContextId, Felt, ProcessState, Word, ONE, ZERO}; use crate::memory; #[test] @@ -38,13 +38,8 @@ fn test_get_balance() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); assert_eq!( process.stack.get(0).as_int(), @@ -72,12 +67,8 @@ fn test_get_balance_non_fungible_fails() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); assert!(process.is_err()); } @@ -102,15 +93,10 @@ fn test_has_non_fungible_asset() { non_fungible_asset_key = prepare_word(&non_fungible_asset.vault_key()) ); - let inputs = + let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - inputs.program().clone(), - inputs.stack_inputs(), - MemAdviceProvider::from(inputs.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); assert_eq!(process.stack.get(0), ONE); } @@ -142,13 +128,8 @@ fn test_add_fungible_asset_success() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); assert_eq!( process.stack.get_word(0), @@ -188,12 +169,8 @@ fn test_add_non_fungible_asset_fail_overflow() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); assert!(process.is_err()); assert!(account_vault.add_asset(add_fungible_asset).is_err()); @@ -229,13 +206,8 @@ fn test_add_non_fungible_asset_success() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); assert_eq!( process.stack.get_word(0), @@ -276,12 +248,8 @@ fn test_add_non_fungible_asset_fail_duplicate() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); assert!(process.is_err()); assert!(account_vault.add_asset(non_fungible_asset).is_err()); @@ -314,13 +282,8 @@ fn test_remove_fungible_asset_success_no_balance_remaining() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); assert_eq!( process.stack.get_word(0), @@ -359,12 +322,9 @@ fn test_remove_fungible_asset_fail_remove_too_much() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ); assert!(process.is_err()); } @@ -395,13 +355,8 @@ fn test_remove_fungible_asset_success_balance_remaining() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); assert_eq!( process.stack.get_word(0), @@ -442,12 +397,8 @@ fn test_remove_non_fungible_asset_fail_doesnt_exist() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); assert!(process.is_err()); assert!(account_vault.remove_asset(non_existent_non_fungible_asset).is_err()); @@ -481,13 +432,8 @@ fn test_remove_non_fungible_asset_success() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); assert_eq!( process.stack.get_word(0), diff --git a/miden-lib/src/tests/test_epilogue.rs b/miden-lib/src/tests/test_epilogue.rs index 7eda506a3..886b2929e 100644 --- a/miden-lib/src/tests/test_epilogue.rs +++ b/miden-lib/src/tests/test_epilogue.rs @@ -12,6 +12,7 @@ use crate::{ outputs::{ CREATED_NOTES_COMMITMENT_WORD_IDX, FINAL_ACCOUNT_HASH_WORD_IDX, TX_SCRIPT_ROOT_WORD_IDX, }, + transaction::ToTransactionKernelInputs, }; const EPILOGUE_FILE: &str = "epilogue.masm"; @@ -36,12 +37,13 @@ fn test_epilogue() { " ); + let (stack_inputs, advice_inputs) = executed_transaction.get_kernel_inputs(); let assembly_file = build_module_path(TX_KERNEL_DIR, EPILOGUE_FILE); let process = run_within_tx_kernel( imports, &code, - executed_transaction.stack_inputs(), - MemAdviceProvider::from(executed_transaction.advice_provider_inputs()), + stack_inputs, + MemAdviceProvider::from(advice_inputs), Some(assembly_file), ) .unwrap(); @@ -96,12 +98,13 @@ fn test_compute_created_note_hash() { " ); + let (stack_inputs, advice_inputs) = executed_transaction.get_kernel_inputs(); let assembly_file = build_module_path(TX_KERNEL_DIR, EPILOGUE_FILE); let process = run_within_tx_kernel( imports, &test, - executed_transaction.stack_inputs(), - MemAdviceProvider::from(executed_transaction.advice_provider_inputs()), + stack_inputs, + MemAdviceProvider::from(advice_inputs), Some(assembly_file), ) .unwrap(); @@ -147,12 +150,13 @@ fn test_epilogue_asset_preservation_violation() { " ); + let (stack_inputs, advice_inputs) = executed_transaction.get_kernel_inputs(); let assembly_file = build_module_path(TX_KERNEL_DIR, EPILOGUE_FILE); let process = run_within_tx_kernel( imports, &code, - executed_transaction.stack_inputs(), - MemAdviceProvider::from(executed_transaction.advice_provider_inputs()), + stack_inputs, + MemAdviceProvider::from(advice_inputs), Some(assembly_file), ); @@ -182,12 +186,13 @@ fn test_epilogue_increment_nonce_success() { " ); + let (stack_inputs, advice_inputs) = executed_transaction.get_kernel_inputs(); let assembly_file = build_module_path(TX_KERNEL_DIR, EPILOGUE_FILE); let _process = run_within_tx_kernel( imports, &code, - executed_transaction.stack_inputs(), - MemAdviceProvider::from(executed_transaction.advice_provider_inputs()), + stack_inputs, + MemAdviceProvider::from(advice_inputs), Some(assembly_file), ) .unwrap(); @@ -213,12 +218,13 @@ fn test_epilogue_increment_nonce_violation() { " ); + let (stack_inputs, advice_inputs) = executed_transaction.get_kernel_inputs(); let assembly_file = build_module_path(TX_KERNEL_DIR, EPILOGUE_FILE); let process = run_within_tx_kernel( imports, &code, - executed_transaction.stack_inputs(), - MemAdviceProvider::from(executed_transaction.advice_provider_inputs()), + stack_inputs, + MemAdviceProvider::from(advice_inputs), Some(assembly_file), ); diff --git a/miden-lib/src/tests/test_faucet.rs b/miden-lib/src/tests/test_faucet.rs index e43aca4e6..d0bf919f0 100644 --- a/miden-lib/src/tests/test_faucet.rs +++ b/miden-lib/src/tests/test_faucet.rs @@ -12,7 +12,7 @@ use mock::{ run_tx, }; -use super::{MemAdviceProvider, ONE}; +use super::{build_tx_inputs, ONE}; use crate::memory::FAUCET_STORAGE_DATA_SLOT; // FUNGIBLE FAUCET MINT TESTS @@ -65,11 +65,8 @@ fn test_mint_fungible_asset_succeeds() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } #[test] @@ -93,10 +90,8 @@ fn test_mint_fungible_asset_fails_not_faucet_account() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); assert!(process.is_err()); } @@ -127,10 +122,9 @@ fn test_mint_fungible_asset_inconsistent_faucet_id() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -162,10 +156,9 @@ fn test_mint_fungible_asset_fails_saturate_max_amount() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -226,11 +219,8 @@ fn test_mint_non_fungible_asset_succeeds() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } #[test] @@ -256,10 +246,9 @@ fn test_mint_non_fungible_asset_fails_not_faucet_account() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -286,10 +275,9 @@ fn test_mint_non_fungible_asset_fails_inconsistent_faucet_id() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -322,10 +310,9 @@ fn test_mint_non_fungible_asset_fails_asset_already_exists() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -380,11 +367,8 @@ fn test_burn_fungible_asset_succeeds() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } #[test] @@ -408,10 +392,9 @@ fn test_burn_fungible_asset_fails_not_faucet_account() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -442,10 +425,8 @@ fn test_burn_fungible_asset_inconsistent_faucet_id() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); assert!(process.is_err()); } @@ -477,10 +458,9 @@ fn test_burn_fungible_asset_insufficient_input_amount() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -541,11 +521,8 @@ fn test_burn_non_fungible_asset_succeeds() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } #[test] @@ -582,10 +559,9 @@ fn test_burn_non_fungible_asset_fails_does_not_exist() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -619,10 +595,9 @@ fn test_burn_non_fungible_asset_fails_not_faucet_account() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -660,10 +635,9 @@ fn test_burn_non_fungible_asset_fails_inconsistent_faucet_id() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider); assert!(process.is_err()); } @@ -703,8 +677,6 @@ fn test_get_total_issuance_succeeds() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); - let _process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), &mut advice_provider) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } diff --git a/miden-lib/src/tests/test_note.rs b/miden-lib/src/tests/test_note.rs index 702a1d6d2..0ba7045ff 100644 --- a/miden-lib/src/tests/test_note.rs +++ b/miden-lib/src/tests/test_note.rs @@ -9,7 +9,7 @@ use mock::{ use vm_core::WORD_SIZE; use super::{ - AdviceProvider, ContextId, DefaultHost, Felt, MemAdviceProvider, Process, ProcessState, ZERO, + build_tx_inputs, AdviceProvider, ContextId, DefaultHost, Felt, Process, ProcessState, ZERO, }; use crate::memory::CURRENT_CONSUMED_NOTE_PTR; @@ -37,12 +37,9 @@ fn test_get_sender_no_sender() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ); assert!(process.is_err()); } @@ -67,13 +64,8 @@ fn test_get_sender() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); let sender = transaction.input_notes().get_note(0).note().metadata().sender().into(); assert_eq!(process.stack.get(0), sender); @@ -125,14 +117,8 @@ fn test_get_vault_data() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - // run to ensure success - let _process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } #[test] @@ -236,7 +222,7 @@ fn test_get_assets() { NOTE_1_ASSET_ASSERTIONS = construct_asset_assertions(notes[1].note()), ); - let inputs = prepare_transaction( + let transaction = prepare_transaction( account, None, block_header, @@ -247,13 +233,8 @@ fn test_get_assets() { "", None, ); - - let _process = run_tx( - inputs.program().clone(), - inputs.stack_inputs(), - MemAdviceProvider::from(inputs.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } #[test] @@ -318,7 +299,7 @@ fn test_get_inputs() { NOTE_1_INPUT_ASSERTIONS = construct_input_assertions(notes[0].note()), ); - let inputs = prepare_transaction( + let transaction = prepare_transaction( account, None, block_header, @@ -329,13 +310,8 @@ fn test_get_inputs() { "", None, ); - - let _process = run_tx( - inputs.program().clone(), - inputs.stack_inputs(), - MemAdviceProvider::from(inputs.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } #[test] @@ -353,16 +329,12 @@ fn test_note_setup() { end "; - let inputs = + let transaction = prepare_transaction(account, None, block_header, chain, notes, None, code, "", None); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); - let process = run_tx( - inputs.program().clone(), - inputs.stack_inputs(), - MemAdviceProvider::from(inputs.advice_provider_inputs()), - ) - .unwrap(); - note_setup_stack_assertions(&process, &inputs); + note_setup_stack_assertions(&process, &transaction); note_setup_memory_assertions(&process); } diff --git a/miden-lib/src/tests/test_prologue.rs b/miden-lib/src/tests/test_prologue.rs index 2af7e65db..e8ed2cb84 100644 --- a/miden-lib/src/tests/test_prologue.rs +++ b/miden-lib/src/tests/test_prologue.rs @@ -8,7 +8,7 @@ use mock::{ }; use super::{ - build_module_path, AdviceProvider, ContextId, DefaultHost, Felt, MemAdviceProvider, Process, + build_module_path, build_tx_inputs, AdviceProvider, ContextId, DefaultHost, Felt, Process, ProcessState, Word, TX_KERNEL_DIR, ZERO, }; use crate::{ @@ -60,12 +60,8 @@ fn test_transaction_prologue() { "", Some(assembly_file), ); - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); global_input_memory_assertions(&process, &transaction); block_data_memory_assertions(&process, &transaction); @@ -361,13 +357,8 @@ pub fn test_prologue_create_account() { "", None, ); - - let _process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } #[cfg_attr(not(feature = "testing"), ignore)] @@ -402,12 +393,9 @@ pub fn test_prologue_create_account_valid_fungible_faucet_reserved_slot() { "", None, ); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ); assert!(process.is_ok()); } @@ -443,12 +431,9 @@ pub fn test_prologue_create_account_invalid_fungible_faucet_reserved_slot() { "", None, ); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ); assert!(process.is_err()); } @@ -484,12 +469,9 @@ pub fn test_prologue_create_account_valid_non_fungible_faucet_reserved_slot() { "", None, ); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider); - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ); assert!(process.is_ok()) } @@ -525,12 +507,9 @@ pub fn test_prologue_create_account_invalid_non_fungible_faucet_reserved_slot() "", None, ); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ); + let process = run_tx(program, stack_inputs, advice_provider); assert!(process.is_err()); } @@ -562,15 +541,14 @@ pub fn test_prologue_create_account_invalid_seed() { "", None, ); + let (program, stack_inputs, mut advice_provider) = build_tx_inputs(&transaction); // lets override the seed with an invalid seed to ensure the kernel fails - let mut advice_provider = MemAdviceProvider::from(transaction.advice_provider_inputs()); advice_provider .insert_into_map(account_seed_key, vec![ZERO, ZERO, ZERO, ZERO]) .unwrap(); - let process = - run_tx(transaction.program().clone(), transaction.stack_inputs(), advice_provider); + let process = run_tx(program, stack_inputs, &mut advice_provider); assert!(process.is_err()); } @@ -590,13 +568,8 @@ fn test_get_blk_version() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); assert_eq!(process.stack.get(0), block_header.version()); } @@ -617,13 +590,8 @@ fn test_get_blk_timestamp() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); assert_eq!(process.stack.get(0), block_header.timestamp()); } diff --git a/miden-lib/src/tests/test_tx.rs b/miden-lib/src/tests/test_tx.rs index 6eba4fa10..d9a31e274 100644 --- a/miden-lib/src/tests/test_tx.rs +++ b/miden-lib/src/tests/test_tx.rs @@ -7,7 +7,9 @@ use mock::{ run_tx, run_within_tx_kernel, }; -use super::{ContextId, Felt, MemAdviceProvider, ProcessState, StackInputs, Word, ONE, ZERO}; +use super::{ + build_tx_inputs, ContextId, Felt, MemAdviceProvider, ProcessState, StackInputs, Word, ONE, ZERO, +}; use crate::memory::{ CREATED_NOTE_ASSETS_OFFSET, CREATED_NOTE_METADATA_OFFSET, CREATED_NOTE_RECIPIENT_OFFSET, CREATED_NOTE_SECTION_OFFSET, NUM_CREATED_NOTES_PTR, @@ -45,13 +47,8 @@ fn test_create_note() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let process = run_tx(program, stack_inputs, advice_provider).unwrap(); // assert the number of created notes has been incremented to 1. assert_eq!( @@ -216,11 +213,6 @@ fn test_get_output_notes_hash() { let transaction = prepare_transaction(account, None, block_header, chain, notes, None, &code, "", None); - - let _process = run_tx( - transaction.program().clone(), - transaction.stack_inputs(), - MemAdviceProvider::from(transaction.advice_provider_inputs()), - ) - .unwrap(); + let (program, stack_inputs, advice_provider) = build_tx_inputs(&transaction); + let _process = run_tx(program, stack_inputs, advice_provider).unwrap(); } diff --git a/objects/src/transaction/utils.rs b/miden-lib/src/transaction/inputs.rs similarity index 68% rename from objects/src/transaction/utils.rs rename to miden-lib/src/transaction/inputs.rs index de7ed9d77..1a9d23ac3 100644 --- a/objects/src/transaction/utils.rs +++ b/miden-lib/src/transaction/inputs.rs @@ -1,11 +1,63 @@ -use vm_core::utils::IntoBytes; - -use super::{ - AdviceInputs, Digest, Felt, StackInputs, ToAdviceInputs, TransactionInputs, TransactionScript, - Vec, Word, ZERO, +use miden_objects::{ + transaction::{ExecutedTransaction, PreparedTransaction, TransactionInputs, TransactionScript}, + AdviceInputs, BlockHeader, Digest, Felt, ToAdviceInputs, Word, ZERO, }; +use vm_core::{utils::IntoBytes, StackInputs}; + +// TRANSACTION KERNEL INPUTS +// ================================================================================================ + +pub trait ToTransactionKernelInputs { + /// TODO: add comments + fn get_kernel_inputs(&self) -> (StackInputs, AdviceInputs); +} + +impl ToTransactionKernelInputs for PreparedTransaction { + fn get_kernel_inputs(&self) -> (StackInputs, AdviceInputs) { + let stack_inputs = build_stack_inputs(self.tx_inputs()); + let advice_inputs = build_advice_inputs(self.tx_inputs(), self.tx_script()); + (stack_inputs, advice_inputs) + } +} -// ADVICE INPUT CONSTRUCTORS +impl ToTransactionKernelInputs for ExecutedTransaction { + fn get_kernel_inputs(&self) -> (StackInputs, AdviceInputs) { + let stack_inputs = build_stack_inputs(self.tx_inputs()); + let advice_inputs = build_advice_inputs(self.tx_inputs(), self.tx_script()); + (stack_inputs, advice_inputs) + } +} + +// STACK INPUTS +// ================================================================================================ + +/// Returns the input stack required when executing a transaction. +/// +/// This includes the input notes commitment, the account hash, the account id, and the block hash. +/// +/// Stack: [BH, acct_id, IAH, NC] +/// +/// - BH is the latest known block hash at the time of transaction execution. +/// - acct_id is the account id of the account that the transaction is being executed against. +/// - IAH is the initial account hash of the account that the transaction is being executed against. +/// - NC is the nullifier commitment of the transaction. This is a sequential hash of all +/// (nullifier, ZERO) tuples for the notes consumed in the transaction. +fn build_stack_inputs(tx_inputs: &TransactionInputs) -> StackInputs { + let initial_acct_hash = if tx_inputs.account.is_new() { + Digest::default() + } else { + tx_inputs.account.hash() + }; + + let mut inputs: Vec = Vec::with_capacity(13); + inputs.extend(tx_inputs.input_notes.commitment()); + inputs.extend_from_slice(initial_acct_hash.as_elements()); + inputs.push((tx_inputs.account.id()).into()); + inputs.extend_from_slice(tx_inputs.block_header.hash().as_elements()); + StackInputs::new(inputs) +} + +// ADVICE INPUTS // ================================================================================================ /// Returns the advice inputs required when executing a transaction. @@ -48,14 +100,14 @@ use super::{ /// - PEAK_0 is the first peak in the block chain MMR from the last known block. /// - PEAK_N is the n'th peak in the block chain MMR from the last known block. /// - ACT_ID_SEED3..0 is the account id seed. -pub fn generate_advice_provider_inputs( +fn build_advice_inputs( tx_inputs: &TransactionInputs, - tx_script: &Option, + tx_script: Option<&TransactionScript>, ) -> AdviceInputs { let mut advice_inputs = AdviceInputs::default(); // insert block data - (&tx_inputs.block_header).to_advice_inputs(&mut advice_inputs); + add_block_header_advice(&tx_inputs.block_header, &mut advice_inputs); // insert block chain mmr (&tx_inputs.block_chain).to_advice_inputs(&mut advice_inputs); @@ -86,31 +138,14 @@ pub fn generate_advice_provider_inputs( advice_inputs } -// INPUT STACK CONSTRUCTOR -// ================================================================================================ - -/// Returns the stack inputs required when executing a transaction. -/// -/// This includes the input notes commitment, the account hash, the account id, and the block hash. -/// -/// Stack: [BH, acct_id, IAH, NC] -/// -/// - BH is the latest known block hash at the time of transaction execution. -/// - acct_id is the account id of the account that the transaction is being executed against. -/// - IAH is the initial account hash of the account that the transaction is being executed against. -/// - NC is the nullifier commitment of the transaction. This is a sequential hash of all -/// (nullifier, ZERO) tuples for the notes consumed in the transaction. -pub fn generate_stack_inputs(tx_inputs: &TransactionInputs) -> StackInputs { - let initial_acct_hash = if tx_inputs.account.is_new() { - Digest::default() - } else { - tx_inputs.account.hash() - }; - - let mut inputs: Vec = Vec::with_capacity(13); - inputs.extend(tx_inputs.input_notes.commitment()); - inputs.extend_from_slice(initial_acct_hash.as_elements()); - inputs.push((tx_inputs.account.id()).into()); - inputs.extend_from_slice(tx_inputs.block_header.hash().as_elements()); - StackInputs::new(inputs) +fn add_block_header_advice(header: &BlockHeader, inputs: &mut AdviceInputs) { + inputs.extend_stack(Word::from(header.prev_hash())); + inputs.extend_stack(Word::from(header.chain_root())); + inputs.extend_stack(Word::from(header.account_root())); + inputs.extend_stack(Word::from(header.nullifier_root())); + inputs.extend_stack(Word::from(header.batch_root())); + inputs.extend_stack(Word::from(header.proof_hash())); + inputs.extend_stack([header.block_num().into(), header.version(), header.timestamp(), ZERO]); + inputs.extend_stack([ZERO; 4]); + inputs.extend_stack(Word::from(header.note_root())); } diff --git a/miden-lib/src/transaction/mod.rs b/miden-lib/src/transaction/mod.rs index 950720c98..145a7756b 100644 --- a/miden-lib/src/transaction/mod.rs +++ b/miden-lib/src/transaction/mod.rs @@ -1,2 +1,5 @@ mod account_stub; pub use account_stub::{extract_account_storage_delta, parse_final_account_stub}; + +mod inputs; +pub use inputs::ToTransactionKernelInputs; diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index 055e94e2f..a7a07bab7 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -1,4 +1,7 @@ -use miden_lib::{outputs::TX_SCRIPT_ROOT_WORD_IDX, transaction::extract_account_storage_delta}; +use miden_lib::{ + outputs::TX_SCRIPT_ROOT_WORD_IDX, + transaction::{extract_account_storage_delta, ToTransactionKernelInputs}, +}; use miden_objects::{ accounts::AccountDelta, assembly::ProgramAst, @@ -6,6 +9,7 @@ use miden_objects::{ Felt, Word, WORD_SIZE, }; use vm_core::{Program, StackOutputs, StarkField}; +use vm_processor::ExecutionOptions; use super::{ AccountCode, AccountId, DataStore, Digest, ExecutedTransaction, NoteOrigin, NoteScript, @@ -33,6 +37,7 @@ use crate::{host::EventHandler, TryFromVmResult}; pub struct TransactionExecutor { compiler: TransactionCompiler, data_store: D, + exec_options: ExecutionOptions, } impl TransactionExecutor { @@ -40,8 +45,11 @@ impl TransactionExecutor { // -------------------------------------------------------------------------------------------- /// Creates a new [TransactionExecutor] instance with the specified [DataStore]. pub fn new(data_store: D) -> Self { - let compiler = TransactionCompiler::new(); - Self { compiler, data_store } + Self { + compiler: TransactionCompiler::new(), + data_store, + exec_options: ExecutionOptions::default(), + } } // STATE MUTATORS @@ -134,13 +142,15 @@ impl TransactionExecutor { let transaction = self.prepare_transaction(account_id, block_ref, note_origins, tx_script)?; - let advice_recorder: RecAdviceProvider = transaction.advice_provider_inputs().into(); + let (stack_inputs, advice_inputs) = transaction.get_kernel_inputs(); + let advice_recorder: RecAdviceProvider = advice_inputs.into(); let mut host = TransactionHost::new(advice_recorder); + let result = vm_processor::execute( transaction.program(), - transaction.stack_inputs(), + stack_inputs, &mut host, - Default::default(), + self.exec_options, ) .map_err(TransactionExecutorError::ExecuteTransactionProgramFailed)?; diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 1af078dd7..7a02eb1af 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -1,3 +1,4 @@ +use miden_lib::transaction::ToTransactionKernelInputs; use miden_objects::transaction::{ PreparedTransaction, ProvenTransaction, TransactionOutputs, TransactionWitness, }; @@ -33,16 +34,13 @@ impl TransactionProver { &self, transaction: PreparedTransaction, ) -> Result { - // prove transaction program - let advice_provider: MemAdviceProvider = transaction.advice_provider_inputs().into(); + let (stack_inputs, advice_inputs) = transaction.get_kernel_inputs(); + let advice_provider: MemAdviceProvider = advice_inputs.into(); let mut host = TransactionHost::new(advice_provider); - let (outputs, proof) = prove( - transaction.program(), - transaction.stack_inputs(), - &mut host, - self.proof_options.clone(), - ) - .map_err(TransactionProverError::ProveTransactionProgramFailed)?; + + let (outputs, proof) = + prove(transaction.program(), stack_inputs, &mut host, self.proof_options.clone()) + .map_err(TransactionProverError::ProveTransactionProgramFailed)?; // extract transaction outputs and process transaction data let (advice_provider, _event_handler) = host.into_parts(); diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index 35378959f..040bd36b9 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -1,8 +1,6 @@ -use vm_core::StackInputs; - use super::{ - utils, AdviceInputs, InputNotes, OutputNotes, TransactionId, TransactionInputs, - TransactionOutputs, TransactionScript, TransactionWitness, + AdviceInputs, InputNotes, OutputNotes, TransactionId, TransactionInputs, TransactionOutputs, + TransactionScript, TransactionWitness, }; use crate::{ accounts::{Account, AccountDelta, AccountId, AccountStub}, @@ -136,14 +134,9 @@ impl ExecutedTransaction { &self.account_delta } - /// Returns the stack inputs required when executing the transaction. - pub fn stack_inputs(&self) -> StackInputs { - utils::generate_stack_inputs(&self.tx_inputs) - } - - /// Returns the advice inputs required when executing the transaction. - pub fn advice_provider_inputs(&self) -> AdviceInputs { - utils::generate_advice_provider_inputs(&self.tx_inputs, &self.tx_script) + /// Returns a reference to the inputs for this transaction. + pub fn tx_inputs(&self) -> &TransactionInputs { + &self.tx_inputs } // CONVERSIONS diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index 0956d29cc..1cd6a0a1a 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -18,8 +18,6 @@ mod proven_tx; mod transaction_id; mod tx_script; mod tx_witness; -#[cfg(not(feature = "testing"))] -mod utils; pub use chain_mmr::ChainMmr; pub use event::Event; @@ -32,9 +30,6 @@ pub use transaction_id::TransactionId; pub use tx_script::TransactionScript; pub use tx_witness::TransactionWitness; -#[cfg(feature = "testing")] -pub mod utils; - // CONSTANTS // ================================================================================================ diff --git a/objects/src/transaction/prepared_tx.rs b/objects/src/transaction/prepared_tx.rs index 4024d116a..7a32342b4 100644 --- a/objects/src/transaction/prepared_tx.rs +++ b/objects/src/transaction/prepared_tx.rs @@ -1,6 +1,5 @@ use super::{ - utils, Account, AdviceInputs, BlockHeader, ChainMmr, InputNotes, Program, StackInputs, - TransactionInputs, TransactionScript, + Account, BlockHeader, ChainMmr, InputNotes, Program, TransactionInputs, TransactionScript, }; use crate::TransactionError; @@ -67,21 +66,16 @@ impl PreparedTransaction { } /// Return a reference the transaction script. - pub fn tx_script(&self) -> &Option { - &self.tx_script + pub fn tx_script(&self) -> Option<&TransactionScript> { + self.tx_script.as_ref() } - /// Returns the stack inputs required when executing the transaction. - pub fn stack_inputs(&self) -> StackInputs { - utils::generate_stack_inputs(&self.tx_inputs) + /// Returns a reference to the inputs for this transaction. + pub fn tx_inputs(&self) -> &TransactionInputs { + &self.tx_inputs } - /// Returns the advice inputs required when executing the transaction. - pub fn advice_provider_inputs(&self) -> AdviceInputs { - utils::generate_advice_provider_inputs(&self.tx_inputs, &self.tx_script) - } - - // CONSUMERS + // CONVERSIONS // -------------------------------------------------------------------------------------------- /// Consumes the prepared transaction and returns its parts. From 733c2c817be33c4c4e3023d7c829760672b23963 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Wed, 27 Dec 2023 13:31:35 -0800 Subject: [PATCH 12/21] refactor: move advice inputs construction logic to miden-lib --- miden-lib/Cargo.toml | 4 +- miden-lib/src/tests/mod.rs | 7 +- miden-lib/src/tests/test_note.rs | 3 +- miden-lib/src/transaction/inputs.rs | 308 +++++++++++++++++++------ miden-tx/src/lib.rs | 2 +- mock/src/mock/transaction.rs | 2 +- objects/src/accounts/mod.rs | 51 +--- objects/src/accounts/vault.rs | 26 +-- objects/src/advice.rs | 61 ----- objects/src/block/header.rs | 17 +- objects/src/block/mod.rs | 4 +- objects/src/lib.rs | 12 +- objects/src/notes/mod.rs | 3 +- objects/src/notes/origin.rs | 2 +- objects/src/notes/vault.rs | 4 + objects/src/transaction/chain_mmr.rs | 19 +- objects/src/transaction/executed_tx.rs | 5 +- objects/src/transaction/inputs.rs | 66 +----- objects/src/transaction/mod.rs | 6 +- objects/src/transaction/tx_script.rs | 16 +- 20 files changed, 284 insertions(+), 334 deletions(-) delete mode 100644 objects/src/advice.rs diff --git a/miden-lib/Cargo.toml b/miden-lib/Cargo.toml index eb8098c7b..ae220dd10 100644 --- a/miden-lib/Cargo.toml +++ b/miden-lib/Cargo.toml @@ -14,7 +14,7 @@ rust-version = "1.67" [features] concurrent = ["miden-objects/concurrent", "std"] default = ["std"] -std = ["assembly/std", "miden-objects/std", "miden-stdlib/std", "vm-processor/std", "vm-core/std"] +std = ["assembly/std", "miden-objects/std", "miden-stdlib/std", "vm-processor/std"] # the testing feature is required to enable the account creation pow patch testing = ["miden-objects/testing"] @@ -22,8 +22,6 @@ testing = ["miden-objects/testing"] assembly = { workspace = true } miden-objects = { package = "miden-objects", path = "../objects", default-features = false } miden-stdlib = { workspace = true } -vm-core = { workspace = true } -vm-processor = { workspace = true } [dev-dependencies] miden-objects = { package = "miden-objects", path = "../objects", default-features = false, features = [ diff --git a/miden-lib/src/tests/mod.rs b/miden-lib/src/tests/mod.rs index 5605ad0ea..80f233d89 100644 --- a/miden-lib/src/tests/mod.rs +++ b/miden-lib/src/tests/mod.rs @@ -1,7 +1,10 @@ use std::path::PathBuf; -use miden_objects::transaction::PreparedTransaction; -use vm_core::{crypto::hash::Rpo256 as Hasher, Felt, Program, StackInputs, Word, ONE, ZERO}; +use miden_objects::{ + transaction::PreparedTransaction, + vm::{Program, StackInputs}, + Felt, Hasher, Word, ONE, ZERO, +}; use vm_processor::{ AdviceProvider, ContextId, DefaultHost, MemAdviceProvider, Process, ProcessState, }; diff --git a/miden-lib/src/tests/test_note.rs b/miden-lib/src/tests/test_note.rs index 0ba7045ff..8d5d7aa43 100644 --- a/miden-lib/src/tests/test_note.rs +++ b/miden-lib/src/tests/test_note.rs @@ -1,4 +1,4 @@ -use miden_objects::{notes::Note, transaction::PreparedTransaction}; +use miden_objects::{notes::Note, transaction::PreparedTransaction, WORD_SIZE}; use mock::{ consumed_note_data_ptr, mock::{account::MockAccountType, notes::AssetPreservationStatus, transaction::mock_inputs}, @@ -6,7 +6,6 @@ use mock::{ procedures::prepare_word, run_tx, }; -use vm_core::WORD_SIZE; use super::{ build_tx_inputs, AdviceProvider, ContextId, DefaultHost, Felt, Process, ProcessState, ZERO, diff --git a/miden-lib/src/transaction/inputs.rs b/miden-lib/src/transaction/inputs.rs index 1a9d23ac3..eb8e83911 100644 --- a/miden-lib/src/transaction/inputs.rs +++ b/miden-lib/src/transaction/inputs.rs @@ -1,14 +1,20 @@ use miden_objects::{ - transaction::{ExecutedTransaction, PreparedTransaction, TransactionInputs, TransactionScript}, - AdviceInputs, BlockHeader, Digest, Felt, ToAdviceInputs, Word, ZERO, + accounts::Account, + transaction::{ + ChainMmr, ExecutedTransaction, InputNotes, PreparedTransaction, TransactionInputs, + TransactionScript, + }, + utils::{collections::Vec, vec, IntoBytes}, + vm::{AdviceInputs, StackInputs}, + Digest, Felt, Word, ZERO, }; -use vm_core::{utils::IntoBytes, StackInputs}; // TRANSACTION KERNEL INPUTS // ================================================================================================ +/// Defines how inputs required to execute a transaction kernel can be extracted from self. pub trait ToTransactionKernelInputs { - /// TODO: add comments + /// Returns stack and advice inputs required to execute the transaction kernel. fn get_kernel_inputs(&self) -> (StackInputs, AdviceInputs); } @@ -31,7 +37,7 @@ impl ToTransactionKernelInputs for ExecutedTransaction { // STACK INPUTS // ================================================================================================ -/// Returns the input stack required when executing a transaction. +/// Returns the input stack required for executing a transaction with the specified inputs. /// /// This includes the input notes commitment, the account hash, the account id, and the block hash. /// @@ -60,92 +66,250 @@ fn build_stack_inputs(tx_inputs: &TransactionInputs) -> StackInputs { // ADVICE INPUTS // ================================================================================================ -/// Returns the advice inputs required when executing a transaction. +/// Returns the advice inputs required for executing a transaction with the specified inputs. +/// /// This includes the initial account, an optional account seed (required for new accounts), the /// number of consumed notes, the core consumed note data, and the consumed note inputs. -/// -/// Advice Tape: [acct_id, ZERO, ZERO, nonce, AVR, ASR, ACR, -/// num_cn, -/// CN1_SN, CN1_SR, CN1_IR, CN1_VR, -/// cn1_na, -/// CN1_A1, CN1_A2, ... -/// CN2_SN,CN2_SR, CN2_IR, CN2_VR, -/// cn2_na, -/// CN2_A1, CN2_A2, ... -/// ...] -/// Advice Map: {CHAIN_ROOT: [num_leaves, PEAK_0, ..., PEAK_N], -/// CN1_IH: [CN1_I3, CN1_I2, CN1_I1, CN1_I0], -/// CN2_IH: [CN2_I3, CN2_I2, CN2_I1, CN2_I0], -/// [acct_id, 0, 0, 0]?: [ACT_ID_SEED3, ACT_ID_SEED2, ACT_ID_SEED1, ACT_ID_SEED0], -/// ...} -/// - acct_id is the account id of the account that the transaction is being executed against. -/// - nonce is the account nonce. -/// - AVR is the account vault root. -/// - ASR is the account storage root. -/// - ACR is the account code root. -/// - num_cn is the number of consumed notes. -/// - CN1_SN is the serial number of consumed note 1. -/// - CN1_SR is the script root of consumed note 1. -/// - CN1_IR is the inputs root of consumed note 1. -/// - CN1_VR is the vault root of consumed note 1. -/// - CN1_M is the metadata of consumed note 1. -/// - CN1_A1 is the first asset of consumed note 1. -/// - CN1_A2 is the second asset of consumed note 1. -/// - CN1_IH is the inputs hash of consumed note 1. -/// - CN2_SN is the serial number of consumed note 2. -/// - CN1_I3..0 are the script inputs of consumed note 1. -/// - CN2_I3..0 are the script inputs of consumed note 2. -/// - CHAIN_ROOT is the root of the block chain MMR from the last known block. -/// - num_leaves is the number of leaves in the block chain MMR from the last known block. -/// - PEAK_0 is the first peak in the block chain MMR from the last known block. -/// - PEAK_N is the n'th peak in the block chain MMR from the last known block. -/// - ACT_ID_SEED3..0 is the account id seed. fn build_advice_inputs( tx_inputs: &TransactionInputs, tx_script: Option<&TransactionScript>, ) -> AdviceInputs { let mut advice_inputs = AdviceInputs::default(); - // insert block data - add_block_header_advice(&tx_inputs.block_header, &mut advice_inputs); + // build the advice stack + build_advice_stack(tx_inputs, tx_script, &mut advice_inputs); + + // build the advice map and Merkle store for relevant components + add_chain_mmr_to_advice_inputs(&tx_inputs.block_chain, &mut advice_inputs); + add_account_to_advice_inputs(&tx_inputs.account, &mut advice_inputs); + add_input_notes_to_advice_inputs(&tx_inputs.input_notes, &mut advice_inputs); + add_tx_script_inputs_to_advice_map(tx_script, &mut advice_inputs); + add_account_seed_to_advice_map(tx_inputs, &mut advice_inputs); + + advice_inputs +} - // insert block chain mmr - (&tx_inputs.block_chain).to_advice_inputs(&mut advice_inputs); +// ADVICE STACK BUILDER +// ------------------------------------------------------------------------------------------------ - // insert account data - tx_inputs.account.to_advice_inputs(&mut advice_inputs); +/// Builds the advice stack for the provided transaction inputs. +/// +/// The advice stack is arranged as follows: +/// elements[0..3] = hash of previous block +/// elements[4..7] = chain MMR hash +/// elements[8..11] = account root +/// elements[12..15] = nullifier root +/// elements[16..19] = batch root +/// elements[20..23] = proof hash +/// elements[24..27] = [block_num, version, timestamp, ZERO] +/// elements[28..31] = [ZERO; 4] +/// elements[32..35] = notes root +/// elements[36..39] = [account ID, ZERO, ZERO, account nonce] +/// elements[40..43] = account vault root +/// elements[44..47] = account storage root +/// elements[48..51] = account code root +/// elements[42..56] = account seed, if one was provided; otherwise [ZERO; 4] +fn build_advice_stack( + tx_inputs: &TransactionInputs, + tx_script: Option<&TransactionScript>, + inputs: &mut AdviceInputs, +) { + // push block header info into the stack + let header = &tx_inputs.block_header; + inputs.extend_stack(header.prev_hash()); + inputs.extend_stack(header.chain_root()); + inputs.extend_stack(header.account_root()); + inputs.extend_stack(header.nullifier_root()); + inputs.extend_stack(header.batch_root()); + inputs.extend_stack(header.proof_hash()); + inputs.extend_stack([header.block_num().into(), header.version(), header.timestamp(), ZERO]); + inputs.extend_stack([ZERO; 4]); + inputs.extend_stack(header.note_root()); - // insert consumed notes data to advice stack - (tx_inputs.input_notes).to_advice_inputs(&mut advice_inputs); + // push core account items onto the stack + let account = &tx_inputs.account; + inputs.extend_stack([account.id().into(), ZERO, ZERO, account.nonce()]); + inputs.extend_stack(account.vault().commitment()); + inputs.extend_stack(account.storage().root()); + inputs.extend_stack(account.code().root()); - if let Some(tx_script) = tx_script.as_ref() { - // populate the advice inputs with the transaction script data - tx_script.to_advice_inputs(&mut advice_inputs) + // push tx_script root onto the stack + if let Some(tx_script) = tx_script { + // insert the transaction script hash into the advice stack + inputs.extend_stack(*tx_script.hash()); } else { // if no transaction script is provided, extend the advice stack with an empty transaction // script root - advice_inputs.extend_stack(Word::default()); + inputs.extend_stack(Word::default()); } +} - // insert account id seed into advice map - if let Some(seed) = tx_inputs.account_seed { - advice_inputs.extend_map(vec![( - [tx_inputs.account.id().into(), ZERO, ZERO, ZERO].into_bytes(), - seed.to_vec(), - )]); +/// Inserts the chain MMR data into the provided advice inputs. +/// +/// Inserts the following items into the Merkle store: +/// - Inner nodes of all authentication paths contained in the chain MMR. +/// +/// Inserts the following entries into the advice map: +/// - peaks_hash |-> MMR peaks info +/// +/// where MMR peaks info has the following layout: +/// elements[0] = number of leaves in the MMR +/// elements[1..4] = padding ([Felt::ZERO; 3]) +/// elements[4..] = MMR peak roots +fn add_chain_mmr_to_advice_inputs(mmr: &ChainMmr, inputs: &mut AdviceInputs) { + // add authentication paths from the MMR to the Merkle store + inputs.extend_merkle_store(mmr.inner_nodes()); + + // insert MMR peaks info into the advice map + let peaks = mmr.peaks(); + let mut elements = vec![Felt::new(peaks.num_leaves() as u64), ZERO, ZERO, ZERO]; + elements.extend(peaks.flatten_and_pad_peaks()); + inputs.extend_map([(peaks.hash_peaks().into(), elements)]); +} + +/// Inserts core account data into the provided advice inputs. +/// +/// Inserts the following items into the Merkle store: +/// - The Merkle nodes associated with the storage slots tree. +/// - The Merkle nodes associated with the account vault tree. +/// - The Merkle nodes associated with the account code procedures tree. +/// +/// Inserts the following entries into the advice map: +/// - The storage types commitment |-> storage slot types vector. +/// - The account procedure root |-> procedure index, for each account procedure. +/// - The node |-> (key, value), for all leaf nodes of the asset vault TSMT. +fn add_account_to_advice_inputs(account: &Account, inputs: &mut AdviceInputs) { + // --- account storage ---------------------------------------------------- + let storage = account.storage(); + + // extend the merkle store with the storage items + inputs.extend_merkle_store(account.storage().slots().inner_nodes()); + + // extend advice map with storage types commitment |-> storage types + inputs.extend_map([( + storage.slot_types_commitment().into(), + storage.slot_types().iter().map(Felt::from).collect(), + )]); + + // --- account vault ------------------------------------------------------ + let vault = account.vault(); + + // extend the merkle store with account vault data + inputs.extend_merkle_store(vault.asset_tree().inner_nodes()); + + // populate advice map with tiered merkle tree leaf nodes + // TODO: this currently handles only the upper leaves of the tree + inputs.extend_map( + vault + .asset_tree() + .upper_leaves() + .map(|(node, key, value)| (node.into(), (*key).into_iter().chain(value).collect())), + ); + + // --- account code ------------------------------------------------------- + let code = account.code(); + + // extend the merkle store with account code tree + inputs.extend_merkle_store(code.procedure_tree().inner_nodes()); + + // extend advice map with account proc root |-> proc index + inputs.extend_map( + code.procedure_tree() + .leaves() + .map(|(idx, leaf)| (leaf.into_bytes(), vec![idx.into()])), + ); +} + +/// Populates the advice inputs for all input notes. +/// +/// For each note the authentication path is populated into the Merkle store, the note inputs +/// and vault assets are populated in the advice map. +/// +/// A combined note data vector is also constructed that holds core data for all notes. This +/// combined vector is added to the advice map against the input notes commitment. For each note +/// the following data items are added to the vector: +/// out[0..4] = serial num +/// out[4..8] = script root +/// out[8..12] = input root +/// out[12..16] = vault_hash +/// out[16..20] = metadata +/// out[20..24] = asset_1 +/// out[24..28] = asset_2 +/// ... +/// out[20 + num_assets * 4..] = Word::default() (this is conditional padding only applied +/// if the number of assets is odd) +/// out[-10] = origin.block_number +/// out[-9..-5] = origin.SUB_HASH +/// out[-5..-1] = origin.NOTE_ROOT +/// out[-1] = origin.node_index +/// +/// Inserts the following items into the Merkle store: +/// - The Merkle nodes associated with the note's authentication path. +/// +/// Inserts the following entries into the advice map: +/// - inputs_hash |-> inputs +/// - vault_hash |-> assets +/// - note_hash |-> combined note data +fn add_input_notes_to_advice_inputs(notes: &InputNotes, inputs: &mut AdviceInputs) { + let mut note_data: Vec = Vec::new(); + + note_data.push(Felt::from(notes.num_notes() as u64)); + + for input_note in notes.iter() { + let note = input_note.note(); + let proof = input_note.proof(); + + // insert note inputs and assets into the advice map + inputs.extend_map([(note.inputs().hash().into(), note.inputs().inputs().to_vec())]); + inputs.extend_map([(note.vault().hash().into(), note.vault().to_padded_assets())]); + + // insert note authentication path nodes into the Merkle store + inputs.extend_merkle_store( + proof + .note_path() + .inner_nodes(proof.origin().node_index.value(), note.authentication_hash()) + .unwrap(), + ); + + // add the note elements to the combined vector of note data + note_data.extend(note.serial_num()); + note_data.extend(*note.script().hash()); + note_data.extend(*note.inputs().hash()); + note_data.extend(*note.vault().hash()); + note_data.extend(Word::from(note.metadata())); + + note_data.extend(note.vault().to_padded_assets()); + + note_data.push(proof.origin().block_num.into()); + note_data.extend(*proof.sub_hash()); + note_data.extend(*proof.note_root()); + note_data.push(proof.origin().node_index.value().into()); } - advice_inputs + // insert the combined note data into the advice map + inputs.extend_map([(notes.commitment().into(), note_data)]); } -fn add_block_header_advice(header: &BlockHeader, inputs: &mut AdviceInputs) { - inputs.extend_stack(Word::from(header.prev_hash())); - inputs.extend_stack(Word::from(header.chain_root())); - inputs.extend_stack(Word::from(header.account_root())); - inputs.extend_stack(Word::from(header.nullifier_root())); - inputs.extend_stack(Word::from(header.batch_root())); - inputs.extend_stack(Word::from(header.proof_hash())); - inputs.extend_stack([header.block_num().into(), header.version(), header.timestamp(), ZERO]); - inputs.extend_stack([ZERO; 4]); - inputs.extend_stack(Word::from(header.note_root())); +/// Inserts the following entries into the advice map: +/// - input_hash |-> input, for each tx_script input +fn add_tx_script_inputs_to_advice_map( + tx_script: Option<&TransactionScript>, + inputs: &mut AdviceInputs, +) { + if let Some(tx_script) = tx_script { + inputs.extend_map( + tx_script.inputs().iter().map(|(hash, input)| (hash.into(), input.clone())), + ); + } +} + +/// Inserts the following entries into the advice map: +/// - [account_id, 0, 0, 0] |-> account_seed +fn add_account_seed_to_advice_map(tx_inputs: &TransactionInputs, inputs: &mut AdviceInputs) { + if let Some(account_seed) = tx_inputs.account_seed { + inputs.extend_map(vec![( + [tx_inputs.account.id().into(), ZERO, ZERO, ZERO].into_bytes(), + account_seed.to_vec(), + )]); + } } diff --git a/miden-tx/src/lib.rs b/miden-tx/src/lib.rs index 9b356f3f9..345c2eb35 100644 --- a/miden-tx/src/lib.rs +++ b/miden-tx/src/lib.rs @@ -2,10 +2,10 @@ use miden_lib::SatKernel; pub use miden_objects::transaction::TransactionInputs; use miden_objects::{ accounts::{AccountCode, AccountId}, - assembly::CodeBlock, notes::{NoteOrigin, NoteScript}, transaction::{ExecutedTransaction, PreparedTransaction}, utils::collections::BTreeMap, + vm::CodeBlock, AccountError, Digest, Hasher, }; use vm_core::Program; diff --git a/mock/src/mock/transaction.rs b/mock/src/mock/transaction.rs index 7762930f0..eb06ac726 100644 --- a/mock/src/mock/transaction.rs +++ b/mock/src/mock/transaction.rs @@ -152,6 +152,6 @@ pub fn mock_executed_tx(asset_preservation: AssetPreservationStatus) -> Executed fn build_dummy_tx_program() -> Program { let operations = vec![Operation::Push(Felt::ZERO), Operation::Drop]; - let span = miden_objects::assembly::CodeBlock::new_span(operations); + let span = miden_objects::vm::CodeBlock::new_span(operations); Program::new(span) } diff --git a/objects/src/accounts/mod.rs b/objects/src/accounts/mod.rs index 86ba2e61a..4997ce629 100644 --- a/objects/src/accounts/mod.rs +++ b/objects/src/accounts/mod.rs @@ -7,8 +7,7 @@ use super::{ serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, string::{String, ToString}, }, - AccountError, AdviceInputsBuilder, Digest, Felt, FieldElement, Hasher, StarkField, - ToAdviceInputs, Word, ZERO, + AccountError, Digest, Felt, FieldElement, Hasher, StarkField, Word, ZERO, }; mod account_id; @@ -175,54 +174,6 @@ impl Account { } } -impl ToAdviceInputs for Account { - /// Pushes account data required for transaction execution into the advice inputs target. - /// - /// Pushes an array of field elements representing this account onto the advice stack. - /// The array (elements) is in the following format: - /// elements[0] = account id - /// elements[2..3] = padding ([Felt::ZERO; 2]) - /// elements[3] = account nonce - /// elements[4..8] = account vault root - /// elements[8..12] = storage root - /// elements[12..16] = code root - /// - /// Pushes the following items into the Merkle store: - /// - The Merkle nodes associated with the storage slots tree. - /// - The Merkle nodes associated with the account code procedures tree. - /// - /// Pushes the following items into the advice map: - /// - The storage types commitment -> storage slot types vector - /// - The account code procedure root -> procedure leaf index - fn to_advice_inputs(&self, target: &mut T) { - // push core items onto the stack - target.push_onto_stack(&[self.id.into(), ZERO, ZERO, self.nonce]); - target.push_onto_stack(self.vault.commitment().as_elements()); - target.push_onto_stack(&*self.storage.root()); - target.push_onto_stack(self.code.root().as_elements()); - - // extend the merkle store with the storage items - target.add_merkle_nodes(self.storage.slots().inner_nodes()); - - // extend advice map with storage types commitment -> storage types - target.insert_into_map( - *self.storage().slot_types_commitment(), - self.storage.slot_types().iter().map(Felt::from).collect(), - ); - - // extend the merkle store with account code tree - target.add_merkle_nodes(self.code.procedure_tree().inner_nodes()); - - // extend advice map with (account proc root -> method tree index) - for (idx, leaf) in self.code.procedure_tree().leaves() { - target.insert_into_map(*leaf, vec![idx.into()]); - } - - // extend the advice provider with [AccountVault] inputs - self.vault.to_advice_inputs(target); - } -} - // SERIALIZATION // ================================================================================================ diff --git a/objects/src/accounts/vault.rs b/objects/src/accounts/vault.rs index 9072a4f14..bb7c9068e 100644 --- a/objects/src/accounts/vault.rs +++ b/objects/src/accounts/vault.rs @@ -1,7 +1,7 @@ use super::{ - AccountError, AccountId, AccountType, AdviceInputsBuilder, Asset, ByteReader, ByteWriter, - Deserializable, DeserializationError, Digest, FungibleAsset, NonFungibleAsset, Serializable, - TieredSmt, ToAdviceInputs, ToString, Vec, ZERO, + AccountError, AccountId, AccountType, Asset, ByteReader, ByteWriter, Deserializable, + DeserializationError, Digest, FungibleAsset, NonFungibleAsset, Serializable, TieredSmt, + ToString, Vec, ZERO, }; // ACCOUNT VAULT @@ -79,6 +79,11 @@ impl AccountVault { self.asset_tree.iter().map(|x| Asset::new_unchecked(x.1)) } + /// Returns a reference to the Sparse Merkle tree underling this account vault. + pub fn asset_tree(&self) -> &TieredSmt { + &self.asset_tree + } + // PUBLIC MODIFIERS // -------------------------------------------------------------------------------------------- @@ -230,18 +235,3 @@ impl Deserializable for AccountVault { Self::new(&assets).map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } - -// ADVICE INPUTS INJECTION -// ================================================================================================ - -impl ToAdviceInputs for AccountVault { - fn to_advice_inputs(&self, target: &mut T) { - // extend the merkle store with account vault data - target.add_merkle_nodes(self.asset_tree.inner_nodes()); - - // populate advice map with tiered merkle tree leaf nodes - self.asset_tree.upper_leaves().for_each(|(node, key, value)| { - target.insert_into_map(*node, (*key).into_iter().chain(value).collect()); - }) - } -} diff --git a/objects/src/advice.rs b/objects/src/advice.rs deleted file mode 100644 index 611dda468..000000000 --- a/objects/src/advice.rs +++ /dev/null @@ -1,61 +0,0 @@ -use assembly::utils::IntoBytes; -use miden_crypto::{merkle::MmrPeaks, ZERO}; -pub use vm_processor::AdviceInputs; - -use crate::{crypto::merkle::InnerNodeInfo, utils::collections::Vec, Felt, Word}; - -/// [AdviceInputsBuilder] trait specifies the interface for building advice inputs. -/// The trait provides three methods for building advice inputs: -/// - `push_onto_stack` pushes the given values onto the advice stack. -/// - `insert_into_map` inserts the given values into the advice map. -/// - `add_merkle_nodes` adds the given merkle nodes to the advice merkle store. -pub trait AdviceInputsBuilder { - /// Pushes the given values onto the advice stack. - fn push_onto_stack(&mut self, values: &[Felt]); - - /// Inserts the given values into the advice map. - fn insert_into_map(&mut self, key: Word, values: Vec); - - /// Adds the given merkle nodes to the advice merkle store. - fn add_merkle_nodes>(&mut self, nodes: I); -} - -impl AdviceInputsBuilder for AdviceInputs { - fn push_onto_stack(&mut self, values: &[Felt]) { - self.extend_stack(values.iter().copied()); - } - - fn insert_into_map(&mut self, key: Word, values: Vec) { - self.extend_map([(key.into_bytes(), values)]); - } - - fn add_merkle_nodes>(&mut self, nodes: I) { - self.extend_merkle_store(nodes); - } -} - -/// ToAdviceInputs trait specifies the interface for converting a rust object into advice inputs. -pub trait ToAdviceInputs { - /// Converts the rust object into advice inputs and pushes them onto the given advice inputs - /// builder. - fn to_advice_inputs(&self, target: &mut T); -} - -// ToAdviceInputs IMPLEMENTATIONS -// ================================================================================================= - -impl ToAdviceInputs for MmrPeaks { - fn to_advice_inputs(&self, target: &mut T) { - // create the vector of items to insert into the map - // The vector is in the following format: - // elements[0] = number of leaves in the Mmr - // elements[1..4] = padding ([Felt::ZERO; 3]) - // elements[4..] = Mmr peak roots - let mut elements = vec![Felt::new(self.num_leaves() as u64), ZERO, ZERO, ZERO]; - elements.extend(self.flatten_and_pad_peaks()); - - // insert the Mmr accumulator vector into the advice map against the Mmr root, which acts - // as the key. - target.insert_into_map(self.hash_peaks().into(), elements); - } -} diff --git a/objects/src/block/header.rs b/objects/src/block/header.rs index 628225e6f..71add3005 100644 --- a/objects/src/block/header.rs +++ b/objects/src/block/header.rs @@ -1,4 +1,4 @@ -use super::{AdviceInputsBuilder, Digest, Felt, Hasher, ToAdviceInputs, Vec, ZERO}; +use super::{Digest, Felt, Hasher, Vec, ZERO}; /// The header of a block. It contains metadata about the block, commitments to the current /// state of the chain and the hash of the proof that attests to the integrity of the chain. @@ -181,18 +181,3 @@ impl BlockHeader { Hasher::hash_elements(&elements) } } - -impl ToAdviceInputs for &BlockHeader { - fn to_advice_inputs(&self, target: &mut T) { - // push header data onto the stack - target.push_onto_stack(self.prev_hash.as_elements()); - target.push_onto_stack(self.chain_root.as_elements()); - target.push_onto_stack(self.account_root.as_elements()); - target.push_onto_stack(self.nullifier_root.as_elements()); - target.push_onto_stack(self.batch_root.as_elements()); - target.push_onto_stack(self.proof_hash.as_elements()); - target.push_onto_stack(&[self.block_num.into(), self.version, self.timestamp, ZERO]); - target.push_onto_stack(&[ZERO; 4]); - target.push_onto_stack(self.note_root.as_elements()); - } -} diff --git a/objects/src/block/mod.rs b/objects/src/block/mod.rs index fd2e6ed74..08a3f4ac5 100644 --- a/objects/src/block/mod.rs +++ b/objects/src/block/mod.rs @@ -1,6 +1,4 @@ -use super::{ - utils::collections::Vec, AdviceInputsBuilder, Digest, Felt, Hasher, ToAdviceInputs, ZERO, -}; +use super::{utils::collections::Vec, Digest, Felt, Hasher, ZERO}; mod header; pub use header::BlockHeader; diff --git a/objects/src/lib.rs b/objects/src/lib.rs index 2bf477c55..267843a9c 100644 --- a/objects/src/lib.rs +++ b/objects/src/lib.rs @@ -6,10 +6,6 @@ extern crate alloc; pub mod accounts; -mod advice; -use advice::AdviceInputsBuilder; -pub use advice::{AdviceInputs, ToAdviceInputs}; - pub mod assets; pub mod notes; @@ -33,7 +29,6 @@ pub mod assembly { ast::{AstSerdeOptions, ModuleAst, ProgramAst}, Assembler, AssemblyContext, AssemblyError, }; - pub use vm_core::{code_blocks::CodeBlock, Program}; } pub mod crypto { @@ -42,7 +37,7 @@ pub mod crypto { pub mod utils { pub use miden_crypto::utils::{format, vec}; - pub use vm_core::utils::{collections, string}; + pub use vm_core::utils::{collections, string, IntoBytes}; pub mod serde { pub use miden_crypto::utils::{ @@ -50,3 +45,8 @@ pub mod utils { }; } } + +pub mod vm { + pub use vm_core::{code_blocks::CodeBlock, Program}; + pub use vm_processor::{AdviceInputs, StackInputs}; +} diff --git a/objects/src/notes/mod.rs b/objects/src/notes/mod.rs index d504bcf95..293b95729 100644 --- a/objects/src/notes/mod.rs +++ b/objects/src/notes/mod.rs @@ -1,8 +1,9 @@ use super::{ accounts::AccountId, - assembly::{Assembler, AssemblyContext, CodeBlock, ProgramAst}, + assembly::{Assembler, AssemblyContext, ProgramAst}, assets::Asset, utils::{collections::Vec, string::ToString}, + vm::CodeBlock, Digest, Felt, Hasher, NoteError, Word, WORD_SIZE, ZERO, }; diff --git a/objects/src/notes/origin.rs b/objects/src/notes/origin.rs index c3027c78e..32dd3eb6a 100644 --- a/objects/src/notes/origin.rs +++ b/objects/src/notes/origin.rs @@ -9,7 +9,7 @@ use crate::crypto::merkle::{MerklePath, NodeIndex}; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct NoteOrigin { pub block_num: u32, - pub node_index: NodeIndex, + pub node_index: NodeIndex, // TODO: should be a u32 because the depth is always the same } /// Contains the data required to prove inclusion of a note in the canonical chain. diff --git a/objects/src/notes/vault.rs b/objects/src/notes/vault.rs index a92d5651b..7574b53df 100644 --- a/objects/src/notes/vault.rs +++ b/objects/src/notes/vault.rs @@ -92,6 +92,10 @@ impl NoteVault { self.assets.iter() } + /// Returns all assets represented as a vector of field elements. + /// + /// The vector is padded with ZEROs so that its length is a multiple of 8. This is useful + /// because hashing the returned elements results in the note vault commitment. pub fn to_padded_assets(&self) -> Vec { // if we have an odd number of assets with pad with a single word. let padded_len = if self.assets.len() % 2 == 0 { diff --git a/objects/src/transaction/chain_mmr.rs b/objects/src/transaction/chain_mmr.rs index e5df4de8b..3377cf9e7 100644 --- a/objects/src/transaction/chain_mmr.rs +++ b/objects/src/transaction/chain_mmr.rs @@ -1,6 +1,6 @@ -use super::{AdviceInputsBuilder, Digest, ToAdviceInputs}; +use super::Digest; use crate::{ - crypto::merkle::{MmrPeaks, PartialMmr}, + crypto::merkle::{InnerNodeInfo, MmrPeaks, PartialMmr}, utils::collections::{BTreeMap, Vec}, ChainMmrError, }; @@ -59,16 +59,13 @@ impl ChainMmr { pub fn chain_length(&self) -> usize { self.mmr.forest() } -} - -impl ToAdviceInputs for &ChainMmr { - fn to_advice_inputs(&self, target: &mut T) { - // Add the Mmr nodes to the merkle store - target.add_merkle_nodes(self.mmr.inner_nodes(self.blocks.iter())); - // Extract Mmr accumulator - let peaks = self.mmr.peaks(); + // ITERATORS + // -------------------------------------------------------------------------------------------- - peaks.to_advice_inputs(target); + /// Returns an iterator over the inner nodes of authentication paths contained in this chain + /// MMR. + pub fn inner_nodes(&self) -> impl Iterator + '_ { + self.mmr.inner_nodes(self.blocks.iter()) } } diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index 040bd36b9..f9ec7ac37 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -1,10 +1,9 @@ use super::{ - AdviceInputs, InputNotes, OutputNotes, TransactionId, TransactionInputs, TransactionOutputs, - TransactionScript, TransactionWitness, + AdviceInputs, InputNotes, OutputNotes, Program, TransactionId, TransactionInputs, + TransactionOutputs, TransactionScript, TransactionWitness, }; use crate::{ accounts::{Account, AccountDelta, AccountId, AccountStub}, - assembly::Program, BlockHeader, TransactionError, }; diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index 9556db206..aabdeb19c 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -1,9 +1,6 @@ use core::cell::OnceCell; -use super::{ - AdviceInputsBuilder, BlockHeader, ChainMmr, Digest, Felt, Hasher, ToAdviceInputs, Word, - MAX_INPUT_NOTES_PER_TRANSACTION, -}; +use super::{BlockHeader, ChainMmr, Digest, Felt, Hasher, Word, MAX_INPUT_NOTES_PER_TRANSACTION}; use crate::{ accounts::{validate_account_seed, Account}, notes::{Note, NoteInclusionProof, NoteOrigin, Nullifier}, @@ -139,67 +136,6 @@ impl PartialEq for InputNotes { impl Eq for InputNotes {} -// ADVICE INPUTS -// -------------------------------------------------------------------------------------------- - -impl ToAdviceInputs for InputNotes { - /// Populates the advice inputs for all consumed notes. - /// - /// For each note the authentication path is populated into the Merkle store, the note inputs - /// and vault assets are populated in the advice map. A combined note data vector is also - /// constructed that holds core data for all notes. This combined vector is added to the advice - /// map against the consumed notes commitment. For each note the following data items are added - /// to the vector: - /// out[0..4] = serial num - /// out[4..8] = script root - /// out[8..12] = input root - /// out[12..16] = vault_hash - /// out[16..20] = metadata - /// out[20..24] = asset_1 - /// out[24..28] = asset_2 - /// ... - /// out[20 + num_assets * 4..] = Word::default() (this is conditional padding only applied - /// if the number of assets is odd) - /// out[-10] = origin.block_number - /// out[-9..-5] = origin.SUB_HASH - /// out[-5..-1] = origin.NOTE_ROOT - /// out[-1] = origin.node_index - fn to_advice_inputs(&self, target: &mut T) { - let mut note_data: Vec = Vec::new(); - - note_data.push(Felt::from(self.notes.len() as u64)); - - for recorded_note in &self.notes { - let note = recorded_note.note(); - let proof = recorded_note.proof(); - - note_data.extend(note.serial_num()); - note_data.extend(*note.script().hash()); - note_data.extend(*note.inputs().hash()); - note_data.extend(*note.vault().hash()); - note_data.extend(Word::from(note.metadata())); - - note_data.extend(note.vault().to_padded_assets()); - target.insert_into_map(note.vault().hash().into(), note.vault().to_padded_assets()); - - note_data.push(proof.origin().block_num.into()); - note_data.extend(*proof.sub_hash()); - note_data.extend(*proof.note_root()); - note_data.push(Felt::from(proof.origin().node_index.value())); - target.add_merkle_nodes( - proof - .note_path() - .inner_nodes(proof.origin().node_index.value(), note.authentication_hash()) - .unwrap(), - ); - - target.insert_into_map(note.inputs().hash().into(), note.inputs().inputs().to_vec()); - } - - target.insert_into_map(self.commitment().into(), note_data); - } -} - // SERIALIZATION // ------------------------------------------------------------------------------------------------ diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index 1cd6a0a1a..c99ef867c 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -1,11 +1,9 @@ -use vm_core::{Program, StackInputs}; - use super::{ accounts::{Account, AccountId}, notes::{NoteEnvelope, Nullifier}, utils::collections::Vec, - AdviceInputs, AdviceInputsBuilder, BlockHeader, Digest, Felt, Hasher, StarkField, - ToAdviceInputs, TransactionWitnessError, Word, WORD_SIZE, ZERO, + vm::{AdviceInputs, Program, StackInputs}, + BlockHeader, Digest, Felt, Hasher, StarkField, TransactionWitnessError, Word, WORD_SIZE, ZERO, }; mod chain_mmr; diff --git a/objects/src/transaction/tx_script.rs b/objects/src/transaction/tx_script.rs index 52bb04a22..497d2356e 100644 --- a/objects/src/transaction/tx_script.rs +++ b/objects/src/transaction/tx_script.rs @@ -1,9 +1,9 @@ use super::{Digest, Felt, Word}; use crate::{ - advice::{AdviceInputsBuilder, ToAdviceInputs}, - assembly::{Assembler, AssemblyContext, CodeBlock, ProgramAst}, + assembly::{Assembler, AssemblyContext, ProgramAst}, errors::TransactionError, utils::collections::{BTreeMap, Vec}, + vm::CodeBlock, }; // TRANSACTION SCRIPT @@ -87,15 +87,3 @@ impl TransactionScript { &self.inputs } } - -impl ToAdviceInputs for TransactionScript { - fn to_advice_inputs(&self, target: &mut T) { - // insert the transaction script hash into the advice stack - target.push_onto_stack(self.hash().as_elements()); - - // insert map inputs into the advice map - for (hash, input) in self.inputs.iter() { - target.insert_into_map(**hash, input.clone()); - } - } -} From 5d30eba98049d1657646eade649e38e4fd93731a Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Thu, 28 Dec 2023 21:25:54 -0800 Subject: [PATCH 13/21] refactor: organization of midenlib --- .../main.masm => kernels/transaction.masm} | 6 +- miden-lib/build.rs | 233 +++++++++++------- miden-lib/src/{ => accounts}/faucets/mod.rs | 10 +- miden-lib/src/accounts/mod.rs | 4 + miden-lib/src/{ => accounts}/wallets/mod.rs | 7 +- miden-lib/src/assembler.rs | 14 -- miden-lib/src/lib.rs | 41 +-- miden-lib/src/notes/mod.rs | 50 +--- miden-lib/src/outputs.rs | 11 - miden-lib/src/tests/test_account.rs | 2 +- miden-lib/src/tests/test_asset_vault.rs | 2 +- miden-lib/src/tests/test_epilogue.rs | 10 +- miden-lib/src/tests/test_faucet.rs | 2 +- miden-lib/src/tests/test_note.rs | 2 +- miden-lib/src/tests/test_prologue.rs | 7 +- miden-lib/src/tests/test_tx.rs | 2 +- miden-lib/src/transaction/account_stub.rs | 59 ----- miden-lib/src/{ => transaction}/memory.rs | 2 +- miden-lib/src/transaction/mod.rs | 53 +++- miden-lib/src/transaction/outputs.rs | 114 +++++++++ miden-tx/src/compiler/mod.rs | 8 +- miden-tx/src/executor/mod.rs | 5 +- miden-tx/src/lib.rs | 2 +- miden-tx/src/result.rs | 26 +- miden-tx/src/verifier/mod.rs | 8 +- miden-tx/tests/common/mod.rs | 6 +- miden-tx/tests/faucet_contract_test.rs | 7 +- miden-tx/tests/wallet_test.rs | 2 +- mock/src/builders/mod.rs | 5 +- mock/src/builders/note.rs | 5 +- mock/src/constants.rs | 8 +- mock/src/lib.rs | 6 +- mock/src/mock/account.rs | 2 +- mock/src/mock/transaction.rs | 8 +- 34 files changed, 399 insertions(+), 330 deletions(-) rename miden-lib/asm/{miden/sat/internal/main.masm => kernels/transaction.masm} (99%) rename miden-lib/src/{ => accounts}/faucets/mod.rs (92%) create mode 100644 miden-lib/src/accounts/mod.rs rename miden-lib/src/{ => accounts}/wallets/mod.rs (92%) delete mode 100644 miden-lib/src/assembler.rs delete mode 100644 miden-lib/src/outputs.rs delete mode 100644 miden-lib/src/transaction/account_stub.rs rename miden-lib/src/{ => transaction}/memory.rs (99%) create mode 100644 miden-lib/src/transaction/outputs.rs diff --git a/miden-lib/asm/miden/sat/internal/main.masm b/miden-lib/asm/kernels/transaction.masm similarity index 99% rename from miden-lib/asm/miden/sat/internal/main.masm rename to miden-lib/asm/kernels/transaction.masm index 0f4b59456..feab09034 100644 --- a/miden-lib/asm/miden/sat/internal/main.masm +++ b/miden-lib/asm/kernels/transaction.masm @@ -59,7 +59,7 @@ use.miden::sat::internal::prologue #! - CNC is the commitment to the notes created by the transaction. #! - FAH is the final account hash of the account that the transaction is being #! executed against. -export.main.1 +proc.main.1 # Prologue # --------------------------------------------------------------------------------------------- @@ -139,3 +139,7 @@ export.main.1 exec.epilogue::finalize_transaction # => [TX_SCRIPT_ROOT, CREATED_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH] end + +begin + exec.main +end diff --git a/miden-lib/build.rs b/miden-lib/build.rs index 2c4694271..e4ebff182 100644 --- a/miden-lib/build.rs +++ b/miden-lib/build.rs @@ -12,14 +12,126 @@ use assembly::{ // CONSTANTS // ================================================================================================ -const ASL_DIR_PATH: &str = "assets"; -const ASM_DIR_PATH: &str = "asm"; -const ASM_MIDEN_DIR_PATH: &str = "asm/miden"; -const ASM_SCRIPTS_DIR_PATH: &str = "asm/scripts"; + +const ASSETS_DIR: &str = "assets"; +const ASM_DIR: &str = "asm"; +const ASM_MIDEN_DIR: &str = "miden"; +const ASM_SCRIPTS_DIR: &str = "scripts"; +const ASM_KERNELS_DIR: &str = "kernels"; // PRE-PROCESSING // ================================================================================================ +/// Read and parse the contents from `./asm`. +/// - Compiles contents of asm/miden directory into a Miden library file (.masl) under +/// miden namespace. +/// - Compiles contents of asm/scripts directory into individual .masb files. +#[cfg(not(feature = "docs-rs"))] +fn main() -> io::Result<()> { + // re-build when the MASM code changes + println!("cargo:rerun-if-changed=asm"); + + // Copies the MASM code to the build directory + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let build_dir = env::var("OUT_DIR").unwrap(); + let src = Path::new(&crate_dir).join(ASM_DIR); + let dst = Path::new(&build_dir).to_path_buf(); + copy_directory(src, &dst); + + // set source directory to {OUT_DIR}/asm + let source_dir = dst.join(ASM_DIR); + + // set target directory to {OUT_DIR}/assets + let target_dir = Path::new(&build_dir).join(ASSETS_DIR); + + // compile miden library + compile_miden_lib(&source_dir, &target_dir)?; + + // compile kernel and note scripts + compile_executable_modules(&source_dir.join(ASM_KERNELS_DIR), &target_dir)?; + compile_executable_modules(&source_dir.join(ASM_SCRIPTS_DIR), &target_dir)?; + + Ok(()) +} + +// COMPILE MIDEN LIB +// ================================================================================================ + +fn compile_miden_lib(source_dir: &Path, target_dir: &Path) -> io::Result<()> { + let source_dir = source_dir.join(ASM_MIDEN_DIR); + + // if this build has the testing flag set, modify the code and reduce the cost of proof-of-work + match env::var("CARGO_FEATURE_TESTING") { + Ok(ref s) if s == "1" => { + let constants = source_dir.join("sat/internal/constants.masm"); + let patched = source_dir.join("sat/internal/constants.masm.patched"); + + // scope for file handlers + { + let read = File::open(&constants).unwrap(); + let mut write = File::create(&patched).unwrap(); + let modified = BufReader::new(read).lines().map(decrease_pow); + + for line in modified { + write.write_all(line.unwrap().as_bytes()).unwrap(); + write.write_all(&[b'\n']).unwrap(); + } + write.flush().unwrap(); + } + + fs::remove_file(&constants).unwrap(); + fs::rename(&patched, &constants).unwrap(); + }, + _ => (), + } + + let ns = LibraryNamespace::try_from("miden".to_string()).expect("invalid base namespace"); + let version = Version::try_from(env!("CARGO_PKG_VERSION")).expect("invalid cargo version"); + let miden_lib = MaslLibrary::read_from_dir(source_dir, ns, true, version)?; + + miden_lib.write_to_dir(target_dir)?; + + Ok(()) +} + +fn decrease_pow(line: io::Result) -> io::Result { + let mut line = line?; + if line.starts_with("const.REGULAR_ACCOUNT_SEED_DIGEST_MODULUS") { + line.clear(); + // 2**5 + line.push_str("const.REGULAR_ACCOUNT_SEED_DIGEST_MODULUS=32 # reduced via build.rs"); + } else if line.starts_with("const.FAUCET_ACCOUNT_SEED_DIGEST_MODULUS") { + line.clear(); + // 2**6 + line.push_str("const.FAUCET_ACCOUNT_SEED_DIGEST_MODULUS=64 # reduced via build.rs"); + } + Ok(line) +} + +// COMPILE EXECUTABLE MODULES +// ================================================================================================ + +fn compile_executable_modules(source_dir: &Path, target_dir: &Path) -> io::Result<()> { + for masm_file_path in get_masm_files(source_dir)? { + // read the MASM file, parse it, and serialize the parsed AST to bytes + let ast = ProgramAst::parse(&fs::read_to_string(masm_file_path.clone())?)?; + let bytes = ast.to_bytes(AstSerdeOptions { serialize_imports: true }); + + // TODO: get rid of unwraps + let masb_file_name = masm_file_path.file_name().unwrap().to_str().unwrap(); + let mut masb_file_path = target_dir.join(masb_file_name); + + // write the binary MASM to the output dir + masb_file_path.set_extension("masb"); + fs::write(masb_file_path, bytes)?; + } + + Ok(()) +} + +// HELPER FUNCTIONS +// ================================================================================================ + /// Recursively copies `src` into `dst`. /// /// This function will overwrite the existing files if re-executed. @@ -31,7 +143,7 @@ fn copy_directory, R: AsRef>(src: T, dst: R) { // keep all the files inside the `asm` folder prefix.pop(); - let target_dir = dst.as_ref().join(ASM_DIR_PATH); + let target_dir = dst.as_ref().join(ASM_DIR); if !target_dir.exists() { fs::create_dir_all(target_dir).unwrap(); } @@ -57,36 +169,13 @@ fn copy_directory, R: AsRef>(src: T, dst: R) { } } -fn decrease_pow(line: io::Result) -> io::Result { - let mut line = line?; - if line.starts_with("const.REGULAR_ACCOUNT_SEED_DIGEST_MODULUS") { - line.clear(); - // 2**5 - line.push_str("const.REGULAR_ACCOUNT_SEED_DIGEST_MODULUS=32 # reduced via build.rs"); - } else if line.starts_with("const.FAUCET_ACCOUNT_SEED_DIGEST_MODULUS") { - line.clear(); - // 2**6 - line.push_str("const.FAUCET_ACCOUNT_SEED_DIGEST_MODULUS=64 # reduced via build.rs"); - } - Ok(line) -} - -fn compile_miden_lib(build_dir: &String, dst: PathBuf) -> io::Result<()> { - let namespace = - LibraryNamespace::try_from("miden".to_string()).expect("invalid base namespace"); - let version = Version::try_from(env!("CARGO_PKG_VERSION")).expect("invalid cargo version"); - let midenlib = - MaslLibrary::read_from_dir(dst.join(ASM_MIDEN_DIR_PATH), namespace, true, version)?; - - midenlib.write_to_dir(Path::new(&build_dir).join(ASL_DIR_PATH))?; - - Ok(()) -} - -fn compile_note_scripts(dst: PathBuf) -> io::Result<()> { - let binding = dst.join(ASM_SCRIPTS_DIR_PATH); - let path = Path::new(&binding); +/// Returns a vector with paths to all MASM files in the specified directory. +/// +/// All non-MASM files are skipped. +fn get_masm_files>(dir_path: P) -> io::Result> { + let mut files = Vec::new(); + let path = dir_path.as_ref(); if path.is_dir() { match fs::read_dir(path) { Ok(entries) => { @@ -94,17 +183,9 @@ fn compile_note_scripts(dst: PathBuf) -> io::Result<()> { match entry { Ok(file) => { let file_path = file.path(); - let file_path_str = - file_path.to_str().unwrap_or(""); - let file_name = format!( - "{}.masb", - file_path_str.split('/').last().unwrap().trim_end_matches(".masm") - ); - let note_script_ast = - ProgramAst::parse(&fs::read_to_string(file_path)?)?; - let note_script_bytes = note_script_ast - .to_bytes(AstSerdeOptions { serialize_imports: true }); - fs::write(dst.join(ASL_DIR_PATH).join(file_name), note_script_bytes)?; + if is_masm_file(&file_path)? { + files.push(file_path); + } }, Err(e) => println!("Error reading directory entry: {}", e), } @@ -116,55 +197,21 @@ fn compile_note_scripts(dst: PathBuf) -> io::Result<()> { println!("cargo:rerun-The specified path is not a directory."); } - Ok(()) + Ok(files) } -/// Read and parse the contents from `./asm`. -/// - Compiles contents of asm/miden directory into a Miden library file (.masl) under -/// miden namespace. -/// - Compiles contents of asm/scripts directory into individual .masb files. -#[cfg(not(feature = "docs-rs"))] -fn main() -> io::Result<()> { - // re-build when the masm code changes. - println!("cargo:rerun-if-changed=asm"); - - // Copies the Masm code to the build directory - let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - let build_dir: String = env::var("OUT_DIR").unwrap(); - let src = Path::new(&crate_dir).join(ASM_DIR_PATH); - let dst = Path::new(&build_dir).to_path_buf(); - copy_directory(src, &dst); - - // if this build has the testing flag set, modify the code and reduce the cost of proof-of-work - match env::var("CARGO_FEATURE_TESTING") { - Ok(ref s) if s == "1" => { - let constants = dst.join(ASM_MIDEN_DIR_PATH).join("sat/internal/constants.masm"); - let patched = dst.join(ASM_MIDEN_DIR_PATH).join("sat/internal/constants.masm.patched"); - - // scope for file handlers - { - let read = File::open(&constants).unwrap(); - let mut write = File::create(&patched).unwrap(); - let modified = BufReader::new(read).lines().map(decrease_pow); - - for line in modified { - write.write_all(line.unwrap().as_bytes()).unwrap(); - write.write_all(&[b'\n']).unwrap(); - } - write.flush().unwrap(); - } - - fs::remove_file(&constants).unwrap(); - fs::rename(&patched, &constants).unwrap(); - }, - _ => (), +/// Returns true if the provided path resolves to a file with `.masm` extension. +/// +/// # Errors +/// Returns an error if the path could not be converted to a UTF-8 string. +fn is_masm_file(path: &Path) -> io::Result { + if let Some(extension) = path.extension() { + let extension = extension + .to_str() + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid UTF-8 filename"))? + .to_lowercase(); + Ok(extension == "masm") + } else { + Ok(false) } - - // compile the stdlib - compile_miden_lib(&build_dir, dst.clone())?; - - // compile the note scripts separately because they are not part of the stdlib - compile_note_scripts(dst)?; - - Ok(()) } diff --git a/miden-lib/src/faucets/mod.rs b/miden-lib/src/accounts/faucets/mod.rs similarity index 92% rename from miden-lib/src/faucets/mod.rs rename to miden-lib/src/accounts/faucets/mod.rs index daa0cfb62..4e97911af 100644 --- a/miden-lib/src/faucets/mod.rs +++ b/miden-lib/src/accounts/faucets/mod.rs @@ -8,8 +8,10 @@ use miden_objects::{ AccountError, Felt, StarkField, Word, ZERO, }; -use super::Library; -use crate::{assembler::assembler, auth::AuthScheme}; +use super::{AuthScheme, Library, MidenLib, TransactionKernel}; + +// FUNGIBLE FAUCET +// ================================================================================================ const MAX_MAX_SUPPLY: u64 = (1 << 63) - 1; const MAX_DECIMALS: u8 = 12; @@ -40,13 +42,13 @@ pub fn create_basic_fungible_faucet( AuthScheme::RpoFalcon512 { pub_key } => pub_key.into(), }; - let miden = super::MidenLib::default(); + let miden = MidenLib::default(); let path = "miden::faucets::basic_fungible"; let faucet_code_ast = miden .get_module_ast(&LibraryPath::new(path).unwrap()) .expect("Getting module AST failed"); - let account_assembler = assembler(); + let account_assembler = TransactionKernel::assembler(); let account_code = AccountCode::new(faucet_code_ast.clone(), &account_assembler)?; // First check that the metadata is valid. diff --git a/miden-lib/src/accounts/mod.rs b/miden-lib/src/accounts/mod.rs new file mode 100644 index 000000000..e62d68c12 --- /dev/null +++ b/miden-lib/src/accounts/mod.rs @@ -0,0 +1,4 @@ +use super::{auth::AuthScheme, transaction::TransactionKernel, Library, MidenLib}; + +pub mod faucets; +pub mod wallets; diff --git a/miden-lib/src/wallets/mod.rs b/miden-lib/src/accounts/wallets/mod.rs similarity index 92% rename from miden-lib/src/wallets/mod.rs rename to miden-lib/src/accounts/wallets/mod.rs index 28fd4c2cc..02d602de5 100644 --- a/miden-lib/src/wallets/mod.rs +++ b/miden-lib/src/accounts/wallets/mod.rs @@ -11,7 +11,10 @@ use miden_objects::{ AccountError, Word, ZERO, }; -use crate::{assembler::assembler, auth::AuthScheme}; +use super::{AuthScheme, TransactionKernel}; + +// BASIC WALLET +// ================================================================================================ /// Creates a new account with basic wallet interface and the specified authentication scheme. /// Basic wallets can be specified to have either mutable or immutable code. @@ -54,7 +57,7 @@ pub fn create_basic_wallet( let account_code_ast = ModuleAst::parse(account_code_src) .map_err(|e| AccountError::AccountCodeAssemblerError(e.into()))?; - let account_assembler = assembler(); + let account_assembler = TransactionKernel::assembler(); let account_code = AccountCode::new(account_code_ast.clone(), &account_assembler)?; let account_storage = AccountStorage::new(vec![( diff --git a/miden-lib/src/assembler.rs b/miden-lib/src/assembler.rs deleted file mode 100644 index 1cf3406ae..000000000 --- a/miden-lib/src/assembler.rs +++ /dev/null @@ -1,14 +0,0 @@ -use assembly::Assembler; -use miden_stdlib::StdLibrary; - -use crate::{MidenLib, SatKernel}; - -pub fn assembler() -> Assembler { - assembly::Assembler::default() - .with_library(&MidenLib::default()) - .expect("failed to load miden-lib") - .with_library(&StdLibrary::default()) - .expect("failed to load std-lib") - .with_kernel(SatKernel::kernel()) - .expect("kernel is well formed") -} diff --git a/miden-lib/src/lib.rs b/miden-lib/src/lib.rs index fbeed374d..a977c7b5d 100644 --- a/miden-lib/src/lib.rs +++ b/miden-lib/src/lib.rs @@ -5,19 +5,15 @@ extern crate alloc; use assembly::{utils::Deserializable, Library, LibraryNamespace, MaslLibrary, Version}; -#[cfg(test)] -mod tests; - mod auth; pub use auth::AuthScheme; -pub mod assembler; -pub mod faucets; -pub mod memory; +pub mod accounts; pub mod notes; -pub mod outputs; pub mod transaction; -pub mod wallets; + +#[cfg(test)] +mod tests; // STANDARD LIBRARY // ================================================================================================ @@ -29,7 +25,7 @@ pub struct MidenLib { impl Default for MidenLib { fn default() -> Self { let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/miden.masl")); - let contents = MaslLibrary::read_from_bytes(bytes).expect("failed to read std masl!"); + let contents = MaslLibrary::read_from_bytes(bytes).expect("failed to read masl!"); Self { contents } } } @@ -53,30 +49,3 @@ impl Library for MidenLib { self.contents.dependencies() } } - -// SINGLE ACCOUNT TRANSACTION (SAT) KERNEL -// ================================================================================================ - -pub struct SatKernel; - -impl SatKernel { - // SAT KERNEL METHODS - // -------------------------------------------------------------------------------------------- - /// Returns masm source code which encodes the transaction kernel system procedures. - pub fn kernel() -> &'static str { - include_str!("../asm/miden/sat/kernel.masm") - } - - // SAT KERNEL MAIN - // -------------------------------------------------------------------------------------------- - /// Returns masm source code which encodes the transaction kernel main procedure. - pub fn main() -> &'static str { - "\ - use.miden::sat::internal::main - - begin - exec.main::main - end - " - } -} diff --git a/miden-lib/src/notes/mod.rs b/miden-lib/src/notes/mod.rs index fe647b3dd..24e8b2d12 100644 --- a/miden-lib/src/notes/mod.rs +++ b/miden-lib/src/notes/mod.rs @@ -2,20 +2,12 @@ use miden_objects::{ accounts::AccountId, assembly::ProgramAst, assets::Asset, - notes::{Note, NoteMetadata, NoteScript, NoteVault}, - transaction::OutputNote, + notes::{Note, NoteScript}, utils::{collections::Vec, vec}, - Digest, Felt, Hasher, NoteError, StarkField, Word, WORD_SIZE, ZERO, + Digest, Felt, Hasher, NoteError, Word, ZERO, }; -use crate::{ - assembler::assembler, - memory::{ - CREATED_NOTE_ASSETS_OFFSET, CREATED_NOTE_CORE_DATA_SIZE, CREATED_NOTE_HASH_OFFSET, - CREATED_NOTE_METADATA_OFFSET, CREATED_NOTE_RECIPIENT_OFFSET, - CREATED_NOTE_VAULT_HASH_OFFSET, - }, -}; +use super::transaction::TransactionKernel; // STANDARDIZED SCRIPTS // ================================================================================================ @@ -37,7 +29,7 @@ pub fn create_note( tag: Option, serial_num: Word, ) -> Result { - let note_assembler = assembler(); + let note_assembler = TransactionKernel::assembler(); // Include the binary version of the scripts into the source file at compile time let p2id_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/P2ID.masb")); @@ -81,43 +73,11 @@ 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 { - if elements.len() < CREATED_NOTE_CORE_DATA_SIZE { - return Err(NoteError::InvalidStubDataLen(elements.len())); - } - - let hash: Digest = elements[CREATED_NOTE_HASH_OFFSET as usize].into(); - let metadata: NoteMetadata = elements[CREATED_NOTE_METADATA_OFFSET as usize].try_into()?; - let recipient = elements[CREATED_NOTE_RECIPIENT_OFFSET as usize].into(); - let vault_hash: Digest = elements[CREATED_NOTE_VAULT_HASH_OFFSET as usize].into(); - - if elements.len() - < (CREATED_NOTE_ASSETS_OFFSET as usize + metadata.num_assets().as_int() as usize) - * WORD_SIZE - { - return Err(NoteError::InvalidStubDataLen(elements.len())); - } - - let vault: NoteVault = elements[CREATED_NOTE_ASSETS_OFFSET as usize - ..(CREATED_NOTE_ASSETS_OFFSET as usize + metadata.num_assets().as_int() as usize)] - .try_into()?; - if vault.hash() != vault_hash { - return Err(NoteError::InconsistentStubVaultHash(vault_hash, vault.hash())); - } - - let stub = OutputNote::new(recipient, vault, metadata); - if stub.hash() != hash { - return Err(NoteError::InconsistentStubHash(stub.hash(), hash)); - } - - Ok(stub) -} - /// Utility function generating RECIPIENT for the P2ID note script created by the SWAP script fn build_p2id_recipient(target: AccountId, serial_num: Word) -> Result { // TODO: add lazy_static initialization or compile-time optimization instead of re-generating // the script hash every time we call the SWAP script - let assembler = assembler(); + let assembler = TransactionKernel::assembler(); let p2id_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/P2ID.masb")); diff --git a/miden-lib/src/outputs.rs b/miden-lib/src/outputs.rs deleted file mode 100644 index 342eb971f..000000000 --- a/miden-lib/src/outputs.rs +++ /dev/null @@ -1,11 +0,0 @@ -// STACK OUTPUTS -// ================================================================================================ - -/// The index of the word at which the transaction script root is stored on the output stack. -pub const TX_SCRIPT_ROOT_WORD_IDX: usize = 0; - -/// The index of the word at which the final account nonce is stored on the output stack. -pub const CREATED_NOTES_COMMITMENT_WORD_IDX: usize = 1; - -/// The index of the word at which the final account hash is stored on the output stack. -pub const FINAL_ACCOUNT_HASH_WORD_IDX: usize = 2; diff --git a/miden-lib/src/tests/test_account.rs b/miden-lib/src/tests/test_account.rs index 8d798cf80..29cd776ff 100644 --- a/miden-lib/src/tests/test_account.rs +++ b/miden-lib/src/tests/test_account.rs @@ -22,7 +22,7 @@ use super::{ super::transaction::ToTransactionKernelInputs, build_tx_inputs, ContextId, Felt, MemAdviceProvider, ProcessState, StackInputs, Word, ONE, ZERO, }; -use crate::memory::{ACCT_CODE_ROOT_PTR, ACCT_NEW_CODE_ROOT_PTR}; +use crate::transaction::memory::{ACCT_CODE_ROOT_PTR, ACCT_NEW_CODE_ROOT_PTR}; // ACCOUNT CODE TESTS // ================================================================================================ diff --git a/miden-lib/src/tests/test_asset_vault.rs b/miden-lib/src/tests/test_asset_vault.rs index 30c0b221f..ddbc1a5e3 100644 --- a/miden-lib/src/tests/test_asset_vault.rs +++ b/miden-lib/src/tests/test_asset_vault.rs @@ -15,7 +15,7 @@ use mock::{ }; use super::{build_tx_inputs, ContextId, Felt, ProcessState, Word, ONE, ZERO}; -use crate::memory; +use crate::transaction::memory; #[test] fn test_get_balance() { diff --git a/miden-lib/src/tests/test_epilogue.rs b/miden-lib/src/tests/test_epilogue.rs index 886b2929e..54bb0152a 100644 --- a/miden-lib/src/tests/test_epilogue.rs +++ b/miden-lib/src/tests/test_epilogue.rs @@ -7,12 +7,10 @@ use mock::{ use super::{ build_module_path, ContextId, MemAdviceProvider, ProcessState, Word, TX_KERNEL_DIR, ZERO, }; -use crate::{ +use crate::transaction::{ memory::{CREATED_NOTE_SECTION_OFFSET, CREATED_NOTE_VAULT_HASH_OFFSET, NOTE_MEM_SIZE}, - outputs::{ - CREATED_NOTES_COMMITMENT_WORD_IDX, FINAL_ACCOUNT_HASH_WORD_IDX, TX_SCRIPT_ROOT_WORD_IDX, - }, - transaction::ToTransactionKernelInputs, + ToTransactionKernelInputs, FINAL_ACCOUNT_HASH_WORD_IDX, OUTPUT_NOTES_COMMITMENT_WORD_IDX, + TX_SCRIPT_ROOT_WORD_IDX, }; const EPILOGUE_FILE: &str = "epilogue.masm"; @@ -59,7 +57,7 @@ fn test_epilogue() { // assert created notes commitment is correct assert_eq!( - process.stack.get_word(CREATED_NOTES_COMMITMENT_WORD_IDX), + process.stack.get_word(OUTPUT_NOTES_COMMITMENT_WORD_IDX), executed_transaction.output_notes().commitment().as_elements() ); diff --git a/miden-lib/src/tests/test_faucet.rs b/miden-lib/src/tests/test_faucet.rs index d0bf919f0..4b4d27b97 100644 --- a/miden-lib/src/tests/test_faucet.rs +++ b/miden-lib/src/tests/test_faucet.rs @@ -13,7 +13,7 @@ use mock::{ }; use super::{build_tx_inputs, ONE}; -use crate::memory::FAUCET_STORAGE_DATA_SLOT; +use crate::transaction::memory::FAUCET_STORAGE_DATA_SLOT; // FUNGIBLE FAUCET MINT TESTS // ================================================================================================ diff --git a/miden-lib/src/tests/test_note.rs b/miden-lib/src/tests/test_note.rs index 8d5d7aa43..bc775af5b 100644 --- a/miden-lib/src/tests/test_note.rs +++ b/miden-lib/src/tests/test_note.rs @@ -10,7 +10,7 @@ use mock::{ use super::{ build_tx_inputs, AdviceProvider, ContextId, DefaultHost, Felt, Process, ProcessState, ZERO, }; -use crate::memory::CURRENT_CONSUMED_NOTE_PTR; +use crate::transaction::memory::CURRENT_CONSUMED_NOTE_PTR; #[test] fn test_get_sender_no_sender() { diff --git a/miden-lib/src/tests/test_prologue.rs b/miden-lib/src/tests/test_prologue.rs index e8ed2cb84..0cadc8435 100644 --- a/miden-lib/src/tests/test_prologue.rs +++ b/miden-lib/src/tests/test_prologue.rs @@ -11,8 +11,7 @@ use super::{ build_module_path, build_tx_inputs, AdviceProvider, ContextId, DefaultHost, Felt, Process, ProcessState, Word, TX_KERNEL_DIR, ZERO, }; -use crate::{ - assembler::assembler, +use crate::transaction::{ memory::{ ACCT_CODE_ROOT_PTR, ACCT_DB_ROOT_PTR, ACCT_ID_AND_NONCE_PTR, ACCT_ID_PTR, ACCT_STORAGE_ROOT_PTR, ACCT_STORAGE_SLOT_TYPE_DATA_OFFSET, ACCT_VAULT_ROOT_PTR, @@ -22,6 +21,7 @@ use crate::{ NULLIFIER_COM_PTR, NULLIFIER_DB_ROOT_PTR, PREV_BLOCK_HASH_PTR, PROOF_HASH_PTR, PROTOCOL_VERSION_IDX, TIMESTAMP_IDX, TX_SCRIPT_ROOT_PTR, }, + TransactionKernel, }; const PROLOGUE_FILE: &str = "prologue.masm"; @@ -46,7 +46,8 @@ fn test_transaction_prologue() { ) .unwrap(); let (tx_script, _) = - TransactionScript::new(mock_tx_script_code, vec![], &mut assembler()).unwrap(); + TransactionScript::new(mock_tx_script_code, vec![], &mut TransactionKernel::assembler()) + .unwrap(); let assembly_file = build_module_path(TX_KERNEL_DIR, PROLOGUE_FILE); let transaction = prepare_transaction( diff --git a/miden-lib/src/tests/test_tx.rs b/miden-lib/src/tests/test_tx.rs index d9a31e274..9e4cb3875 100644 --- a/miden-lib/src/tests/test_tx.rs +++ b/miden-lib/src/tests/test_tx.rs @@ -10,7 +10,7 @@ use mock::{ use super::{ build_tx_inputs, ContextId, Felt, MemAdviceProvider, ProcessState, StackInputs, Word, ONE, ZERO, }; -use crate::memory::{ +use crate::transaction::memory::{ CREATED_NOTE_ASSETS_OFFSET, CREATED_NOTE_METADATA_OFFSET, CREATED_NOTE_RECIPIENT_OFFSET, CREATED_NOTE_SECTION_OFFSET, NUM_CREATED_NOTES_PTR, }; diff --git a/miden-lib/src/transaction/account_stub.rs b/miden-lib/src/transaction/account_stub.rs deleted file mode 100644 index 21cdbcb98..000000000 --- a/miden-lib/src/transaction/account_stub.rs +++ /dev/null @@ -1,59 +0,0 @@ -use miden_objects::{ - accounts::{Account, AccountId, AccountStorage, AccountStorageDelta, AccountStub}, - crypto::merkle::{merkle_tree_delta, MerkleStore}, - AccountError, TransactionResultError, Word, -}; - -use crate::memory::{ - ACCT_CODE_ROOT_OFFSET, ACCT_DATA_MEM_SIZE, ACCT_ID_AND_NONCE_OFFSET, ACCT_ID_IDX, - ACCT_NONCE_IDX, ACCT_STORAGE_ROOT_OFFSET, ACCT_VAULT_ROOT_OFFSET, -}; - -/// Parses the stub account data returned by the VM into individual account component commitments. -/// Returns a tuple of account ID, vault root, storage root, code root, and nonce. -pub fn parse_final_account_stub(elements: &[Word]) -> Result { - if elements.len() != ACCT_DATA_MEM_SIZE { - return Err(AccountError::StubDataIncorrectLength(elements.len(), ACCT_DATA_MEM_SIZE)); - } - - let id = AccountId::try_from(elements[ACCT_ID_AND_NONCE_OFFSET as usize][ACCT_ID_IDX])?; - let nonce = elements[ACCT_ID_AND_NONCE_OFFSET as usize][ACCT_NONCE_IDX]; - let vault_root = elements[ACCT_VAULT_ROOT_OFFSET as usize].into(); - let storage_root = elements[ACCT_STORAGE_ROOT_OFFSET as usize].into(); - let code_root = elements[ACCT_CODE_ROOT_OFFSET as usize].into(); - - Ok(AccountStub::new(id, nonce, vault_root, storage_root, code_root)) -} - -// ACCOUNT STORAGE DELTA -// ================================================================================================ -/// Extracts account storage delta between the `initial_account` and `final_account_stub` from the -/// provided `MerkleStore` -pub fn extract_account_storage_delta( - store: &MerkleStore, - initial_account: &Account, - final_account_stub: &AccountStub, -) -> Result { - // extract storage slots delta - let tree_delta = merkle_tree_delta( - initial_account.storage().root(), - final_account_stub.storage_root(), - AccountStorage::STORAGE_TREE_DEPTH, - store, - ) - .map_err(TransactionResultError::ExtractAccountStorageSlotsDeltaFailed)?; - - // map tree delta to cleared/updated slots; we can cast indexes to u8 because the - // the number of storage slots cannot be greater than 256 - let cleared_items = tree_delta.cleared_slots().iter().map(|idx| *idx as u8).collect(); - let updated_items = tree_delta - .updated_slots() - .iter() - .map(|(idx, value)| (*idx as u8, *value)) - .collect(); - - // construct storage delta - let storage_delta = AccountStorageDelta { cleared_items, updated_items }; - - Ok(storage_delta) -} diff --git a/miden-lib/src/memory.rs b/miden-lib/src/transaction/memory.rs similarity index 99% rename from miden-lib/src/memory.rs rename to miden-lib/src/transaction/memory.rs index 815922d92..2e33ad9e4 100644 --- a/miden-lib/src/memory.rs +++ b/miden-lib/src/transaction/memory.rs @@ -218,5 +218,5 @@ pub const CREATED_NOTE_RECIPIENT_OFFSET: MemoryOffset = 2; pub const CREATED_NOTE_VAULT_HASH_OFFSET: MemoryOffset = 3; pub const CREATED_NOTE_ASSETS_OFFSET: MemoryOffset = 4; -/// The maximum number of created notes that can be prodcued in a single transaction. +/// The maximum number of created notes that can be produced in a single transaction. pub const MAX_NUM_CREATED_NOTES: u32 = 4096; diff --git a/miden-lib/src/transaction/mod.rs b/miden-lib/src/transaction/mod.rs index 145a7756b..f2ffb96d5 100644 --- a/miden-lib/src/transaction/mod.rs +++ b/miden-lib/src/transaction/mod.rs @@ -1,5 +1,54 @@ -mod account_stub; -pub use account_stub::{extract_account_storage_delta, parse_final_account_stub}; +use assembly::{ast::ProgramAst, utils::DeserializationError, Assembler}; +use miden_stdlib::StdLibrary; + +use super::MidenLib; + +pub mod memory; mod inputs; pub use inputs::ToTransactionKernelInputs; + +mod outputs; +pub use outputs::{ + extract_account_storage_delta, notes_try_from_elements, parse_final_account_stub, + FINAL_ACCOUNT_HASH_WORD_IDX, OUTPUT_NOTES_COMMITMENT_WORD_IDX, TX_SCRIPT_ROOT_WORD_IDX, +}; + +// TRANSACTION KERNEL +// ================================================================================================ + +pub struct TransactionKernel; + +impl TransactionKernel { + // KERNEL SOURCE CODE + // -------------------------------------------------------------------------------------------- + + /// Returns MASM source code which encodes the transaction kernel system procedures. + pub fn kernel() -> &'static str { + include_str!("../../asm/miden/sat/kernel.masm") + } + + /// Returns an AST of the transaction kernel executable program. + /// + /// # Errors + /// Returns an error if deserialization of the binary fails. + pub fn main() -> Result { + let kernel_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/transaction.masb")); + ProgramAst::from_bytes(kernel_bytes) + } + + // ASSEMBLER CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + + /// Returns a new Miden assembler instantiated with the transaction kernel and loaded with the + /// Miden stdlib as well as with midenlib. + pub fn assembler() -> Assembler { + Assembler::default() + .with_library(&MidenLib::default()) + .expect("failed to load miden-lib") + .with_library(&StdLibrary::default()) + .expect("failed to load std-lib") + .with_kernel(Self::kernel()) + .expect("kernel is well formed") + } +} diff --git a/miden-lib/src/transaction/outputs.rs b/miden-lib/src/transaction/outputs.rs new file mode 100644 index 000000000..e13a89019 --- /dev/null +++ b/miden-lib/src/transaction/outputs.rs @@ -0,0 +1,114 @@ +use miden_objects::{ + accounts::{Account, AccountId, AccountStorage, AccountStorageDelta, AccountStub}, + crypto::merkle::{merkle_tree_delta, MerkleStore}, + notes::{NoteMetadata, NoteVault}, + transaction::OutputNote, + AccountError, Digest, NoteError, StarkField, TransactionResultError, Word, WORD_SIZE, +}; + +use super::memory::{ + ACCT_CODE_ROOT_OFFSET, ACCT_DATA_MEM_SIZE, ACCT_ID_AND_NONCE_OFFSET, ACCT_ID_IDX, + ACCT_NONCE_IDX, ACCT_STORAGE_ROOT_OFFSET, ACCT_VAULT_ROOT_OFFSET, CREATED_NOTE_ASSETS_OFFSET, + CREATED_NOTE_CORE_DATA_SIZE, CREATED_NOTE_HASH_OFFSET, CREATED_NOTE_METADATA_OFFSET, + CREATED_NOTE_RECIPIENT_OFFSET, CREATED_NOTE_VAULT_HASH_OFFSET, +}; + +// STACK OUTPUTS +// ================================================================================================ + +/// The index of the word at which the transaction script root is stored on the output stack. +pub const TX_SCRIPT_ROOT_WORD_IDX: usize = 0; + +/// The index of the word at which the final account nonce is stored on the output stack. +pub const OUTPUT_NOTES_COMMITMENT_WORD_IDX: usize = 1; + +/// The index of the word at which the final account hash is stored on the output stack. +pub const FINAL_ACCOUNT_HASH_WORD_IDX: usize = 2; + +// ACCOUNT STUB EXTRACTOR +// ================================================================================================ + +/// Parses the stub account data returned by the VM into individual account component commitments. +/// Returns a tuple of account ID, vault root, storage root, code root, and nonce. +pub fn parse_final_account_stub(elements: &[Word]) -> Result { + if elements.len() != ACCT_DATA_MEM_SIZE { + return Err(AccountError::StubDataIncorrectLength(elements.len(), ACCT_DATA_MEM_SIZE)); + } + + let id = AccountId::try_from(elements[ACCT_ID_AND_NONCE_OFFSET as usize][ACCT_ID_IDX])?; + let nonce = elements[ACCT_ID_AND_NONCE_OFFSET as usize][ACCT_NONCE_IDX]; + let vault_root = elements[ACCT_VAULT_ROOT_OFFSET as usize].into(); + let storage_root = elements[ACCT_STORAGE_ROOT_OFFSET as usize].into(); + let code_root = elements[ACCT_CODE_ROOT_OFFSET as usize].into(); + + Ok(AccountStub::new(id, nonce, vault_root, storage_root, code_root)) +} + +// ACCOUNT STORAGE DELTA EXTRACTOR +// ================================================================================================ + +/// Extracts account storage delta between the `initial_account` and `final_account_stub` from the +/// provided `MerkleStore` +pub fn extract_account_storage_delta( + store: &MerkleStore, + initial_account: &Account, + final_account_stub: &AccountStub, +) -> Result { + // extract storage slots delta + let tree_delta = merkle_tree_delta( + initial_account.storage().root(), + final_account_stub.storage_root(), + AccountStorage::STORAGE_TREE_DEPTH, + store, + ) + .map_err(TransactionResultError::ExtractAccountStorageSlotsDeltaFailed)?; + + // map tree delta to cleared/updated slots; we can cast indexes to u8 because the + // the number of storage slots cannot be greater than 256 + let cleared_items = tree_delta.cleared_slots().iter().map(|idx| *idx as u8).collect(); + let updated_items = tree_delta + .updated_slots() + .iter() + .map(|(idx, value)| (*idx as u8, *value)) + .collect(); + + // construct storage delta + let storage_delta = AccountStorageDelta { cleared_items, updated_items }; + + Ok(storage_delta) +} + +// NOTES EXTRACTOR +// ================================================================================================ + +pub fn notes_try_from_elements(elements: &[Word]) -> Result { + if elements.len() < CREATED_NOTE_CORE_DATA_SIZE { + return Err(NoteError::InvalidStubDataLen(elements.len())); + } + + let hash: Digest = elements[CREATED_NOTE_HASH_OFFSET as usize].into(); + let metadata: NoteMetadata = elements[CREATED_NOTE_METADATA_OFFSET as usize].try_into()?; + let recipient = elements[CREATED_NOTE_RECIPIENT_OFFSET as usize].into(); + let vault_hash: Digest = elements[CREATED_NOTE_VAULT_HASH_OFFSET as usize].into(); + + if elements.len() + < (CREATED_NOTE_ASSETS_OFFSET as usize + metadata.num_assets().as_int() as usize) + * WORD_SIZE + { + return Err(NoteError::InvalidStubDataLen(elements.len())); + } + + let vault: NoteVault = elements[CREATED_NOTE_ASSETS_OFFSET as usize + ..(CREATED_NOTE_ASSETS_OFFSET as usize + metadata.num_assets().as_int() as usize)] + .try_into()?; + if vault.hash() != vault_hash { + return Err(NoteError::InconsistentStubVaultHash(vault_hash, vault.hash())); + } + + let stub = OutputNote::new(recipient, vault, metadata); + if stub.hash() != hash { + return Err(NoteError::InconsistentStubHash(stub.hash(), hash)); + } + + Ok(stub) +} diff --git a/miden-tx/src/compiler/mod.rs b/miden-tx/src/compiler/mod.rs index 0592e9f8a..332098850 100644 --- a/miden-tx/src/compiler/mod.rs +++ b/miden-tx/src/compiler/mod.rs @@ -6,8 +6,8 @@ use miden_objects::{ use vm_processor::ProgramInfo; use super::{ - AccountCode, AccountId, BTreeMap, CodeBlock, Digest, NoteScript, Program, SatKernel, - TransactionCompilerError, + AccountCode, AccountId, BTreeMap, CodeBlock, Digest, NoteScript, Program, + TransactionCompilerError, TransactionKernel, }; #[cfg(test)] @@ -35,10 +35,10 @@ impl TransactionCompiler { // -------------------------------------------------------------------------------------------- /// Returns a new instance of the [TransactionComplier]. pub fn new() -> TransactionCompiler { - let assembler = miden_lib::assembler::assembler(); + let assembler = TransactionKernel::assembler(); // compile transaction kernel main - let main_ast = ProgramAst::parse(SatKernel::main()).expect("main is well formed"); + let main_ast = TransactionKernel::main().expect("main is well formed"); let kernel_main = assembler .compile_in_context(&main_ast, &mut AssemblyContext::for_program(Some(&main_ast))) .expect("main is well formed"); diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index a7a07bab7..969f0bb4e 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -1,6 +1,5 @@ -use miden_lib::{ - outputs::TX_SCRIPT_ROOT_WORD_IDX, - transaction::{extract_account_storage_delta, ToTransactionKernelInputs}, +use miden_lib::transaction::{ + extract_account_storage_delta, ToTransactionKernelInputs, TX_SCRIPT_ROOT_WORD_IDX, }; use miden_objects::{ accounts::AccountDelta, diff --git a/miden-tx/src/lib.rs b/miden-tx/src/lib.rs index 345c2eb35..2d8da2e33 100644 --- a/miden-tx/src/lib.rs +++ b/miden-tx/src/lib.rs @@ -1,4 +1,4 @@ -use miden_lib::SatKernel; +use miden_lib::transaction::TransactionKernel; pub use miden_objects::transaction::TransactionInputs; use miden_objects::{ accounts::{AccountCode, AccountId}, diff --git a/miden-tx/src/result.rs b/miden-tx/src/result.rs index 861461edf..ba03ac584 100644 --- a/miden-tx/src/result.rs +++ b/miden-tx/src/result.rs @@ -1,8 +1,6 @@ -use miden_lib::{ - memory::NOTE_MEM_SIZE, - notes::notes_try_from_elements, - outputs::{CREATED_NOTES_COMMITMENT_WORD_IDX, FINAL_ACCOUNT_HASH_WORD_IDX}, - transaction::parse_final_account_stub, +use miden_lib::transaction::{ + memory::NOTE_MEM_SIZE, notes_try_from_elements, parse_final_account_stub, + FINAL_ACCOUNT_HASH_WORD_IDX, OUTPUT_NOTES_COMMITMENT_WORD_IDX, }; use miden_objects::{ accounts::AccountStub, @@ -53,15 +51,15 @@ impl TryFromVmResult for OutputNotes { advice_map: &BTreeMap<[u8; 32], Vec>, _merkle_store: &MerkleStore, ) -> Result { - let created_notes_commitment: Word = - stack_outputs.stack()[CREATED_NOTES_COMMITMENT_WORD_IDX * WORD_SIZE - ..(CREATED_NOTES_COMMITMENT_WORD_IDX + 1) * WORD_SIZE] - .iter() - .rev() - .map(|x| Felt::from(*x)) - .collect::>() - .try_into() - .expect("word size is correct"); + let created_notes_commitment: Word = stack_outputs.stack()[OUTPUT_NOTES_COMMITMENT_WORD_IDX + * WORD_SIZE + ..(OUTPUT_NOTES_COMMITMENT_WORD_IDX + 1) * WORD_SIZE] + .iter() + .rev() + .map(|x| Felt::from(*x)) + .collect::>() + .try_into() + .expect("word size is correct"); let created_notes_commitment: Digest = created_notes_commitment.into(); let created_notes_data = group_slice_elements::( diff --git a/miden-tx/src/verifier/mod.rs b/miden-tx/src/verifier/mod.rs index 924e9024d..469ee7ddc 100644 --- a/miden-tx/src/verifier/mod.rs +++ b/miden-tx/src/verifier/mod.rs @@ -1,7 +1,7 @@ use core::ops::Range; -use miden_lib::outputs::{ - CREATED_NOTES_COMMITMENT_WORD_IDX, FINAL_ACCOUNT_HASH_WORD_IDX, TX_SCRIPT_ROOT_WORD_IDX, +use miden_lib::transaction::{ + FINAL_ACCOUNT_HASH_WORD_IDX, OUTPUT_NOTES_COMMITMENT_WORD_IDX, TX_SCRIPT_ROOT_WORD_IDX, }; use miden_objects::{ notes::{NoteEnvelope, Nullifier}, @@ -97,8 +97,8 @@ impl TransactionVerifier { end: STACK_TOP_SIZE - (TX_SCRIPT_ROOT_WORD_IDX * WORD_SIZE), }; const CREATED_NOTES_COMMITMENT_RANGE: Range = Range { - start: STACK_TOP_SIZE - ((CREATED_NOTES_COMMITMENT_WORD_IDX + 1) * WORD_SIZE), - end: STACK_TOP_SIZE - (CREATED_NOTES_COMMITMENT_WORD_IDX * WORD_SIZE), + start: STACK_TOP_SIZE - ((OUTPUT_NOTES_COMMITMENT_WORD_IDX + 1) * WORD_SIZE), + end: STACK_TOP_SIZE - (OUTPUT_NOTES_COMMITMENT_WORD_IDX * WORD_SIZE), }; const FINAL_ACCOUNT_HASH_RANGE: Range = Range { start: STACK_TOP_SIZE - ((FINAL_ACCOUNT_HASH_WORD_IDX + 1) * WORD_SIZE), diff --git a/miden-tx/tests/common/mod.rs b/miden-tx/tests/common/mod.rs index 2290ea479..a72af5645 100644 --- a/miden-tx/tests/common/mod.rs +++ b/miden-tx/tests/common/mod.rs @@ -1,4 +1,4 @@ -use miden_lib::assembler::assembler; +use miden_lib::transaction::TransactionKernel; use miden_objects::{ accounts::{Account, AccountCode, AccountId, AccountStorage, AccountVault, StorageSlotType}, assembly::{ModuleAst, ProgramAst}, @@ -120,7 +120,7 @@ pub fn get_account_with_default_account_code( ) -> Account { let account_code_src = DEFAULT_ACCOUNT_CODE; let account_code_ast = ModuleAst::parse(account_code_src).unwrap(); - let account_assembler = assembler(); + let account_assembler = TransactionKernel::assembler(); let account_code = AccountCode::new(account_code_ast.clone(), &account_assembler).unwrap(); let account_storage = @@ -140,7 +140,7 @@ pub fn get_note_with_fungible_asset_and_script( fungible_asset: FungibleAsset, note_script: ProgramAst, ) -> Note { - let note_assembler = assembler(); + let note_assembler = TransactionKernel::assembler(); let (note_script, _) = NoteScript::new(note_script, ¬e_assembler).unwrap(); const SERIAL_NUM: Word = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; let sender_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); diff --git a/miden-tx/tests/faucet_contract_test.rs b/miden-tx/tests/faucet_contract_test.rs index c0533cde1..b13eb5889 100644 --- a/miden-tx/tests/faucet_contract_test.rs +++ b/miden-tx/tests/faucet_contract_test.rs @@ -1,5 +1,6 @@ use miden_lib::{ - assembler::assembler, faucets::create_basic_fungible_faucet, memory::FAUCET_STORAGE_DATA_SLOT, + accounts::faucets::create_basic_fungible_faucet, + transaction::{memory::FAUCET_STORAGE_DATA_SLOT, TransactionKernel}, AuthScheme, }; use miden_objects::{ @@ -242,7 +243,7 @@ fn test_faucet_contract_creation() { let exp_faucet_account_code_src = include_str!("../../miden-lib/asm/miden/faucets/basic_fungible.masm"); let exp_faucet_account_code_ast = ModuleAst::parse(exp_faucet_account_code_src).unwrap(); - let account_assembler = assembler(); + let account_assembler = TransactionKernel::assembler(); let exp_faucet_account_code = AccountCode::new(exp_faucet_account_code_ast.clone(), &account_assembler).unwrap(); @@ -259,7 +260,7 @@ fn get_faucet_account_with_max_supply_and_total_issuance( let faucet_account_code_src = include_str!("../../miden-lib/asm/miden/faucets/basic_fungible.masm"); let faucet_account_code_ast = ModuleAst::parse(faucet_account_code_src).unwrap(); - let account_assembler = assembler(); + let account_assembler = TransactionKernel::assembler(); let faucet_account_code = AccountCode::new(faucet_account_code_ast.clone(), &account_assembler).unwrap(); diff --git a/miden-tx/tests/wallet_test.rs b/miden-tx/tests/wallet_test.rs index 0b813d957..cbeca2302 100644 --- a/miden-tx/tests/wallet_test.rs +++ b/miden-tx/tests/wallet_test.rs @@ -1,4 +1,4 @@ -use miden_lib::{wallets::create_basic_wallet, AuthScheme}; +use miden_lib::{accounts::wallets::create_basic_wallet, AuthScheme}; use miden_objects::{ accounts::{Account, AccountId, AccountStorage, AccountVault, StorageSlotType}, assembly::ProgramAst, diff --git a/mock/src/builders/mod.rs b/mock/src/builders/mod.rs index e516bcf9c..d2216e06e 100644 --- a/mock/src/builders/mod.rs +++ b/mock/src/builders/mod.rs @@ -1,6 +1,7 @@ -use miden_lib::assembler::assembler; use miden_objects::{accounts::AccountCode, assembly::ModuleAst, AccountError}; +use super::TransactionKernel; + mod account; mod account_id; mod account_storage; @@ -20,7 +21,7 @@ pub use nonfungible_asset::{NonFungibleAssetBuilder, NonFungibleAssetDetailsBuil pub use note::NoteBuilder; pub fn str_to_account_code(source: &str) -> Result { - let assembler = assembler(); + let assembler = TransactionKernel::assembler(); let account_module_ast = ModuleAst::parse(source).unwrap(); AccountCode::new(account_module_ast, &assembler) } diff --git a/mock/src/builders/note.rs b/mock/src/builders/note.rs index fe0c39f31..5ded927d1 100644 --- a/mock/src/builders/note.rs +++ b/mock/src/builders/note.rs @@ -1,4 +1,3 @@ -use miden_lib::assembler::assembler; use miden_objects::{ accounts::AccountId, assembly::ProgramAst, @@ -12,6 +11,8 @@ use miden_objects::{ }; use rand::Rng; +use super::TransactionKernel; + const DEFAULT_NOTE_CODE: &str = "\ begin end @@ -76,7 +77,7 @@ impl NoteBuilder { } pub fn build(self) -> Result { - let assembler = assembler(); + let assembler = TransactionKernel::assembler(); let note_ast = ProgramAst::parse(&self.code).unwrap(); let (note_script, _) = NoteScript::new(note_ast, &assembler)?; Note::new(note_script, &self.inputs, &self.assets, self.serial_num, self.sender, self.tag) diff --git a/mock/src/constants.rs b/mock/src/constants.rs index 3294997b1..4dd92ef7a 100644 --- a/mock/src/constants.rs +++ b/mock/src/constants.rs @@ -1,15 +1,17 @@ -use miden_lib::assembler::assembler; use miden_objects::{ accounts::{AccountId, AccountType, SlotItem, StorageSlotType}, assets::{Asset, NonFungibleAsset, NonFungibleAssetDetails}, Felt, FieldElement, Word, ZERO, }; -use super::mock::account::{mock_account, mock_fungible_faucet, mock_non_fungible_faucet}; pub use super::mock::account::{ ACCOUNT_PROCEDURE_INCR_NONCE_PROC_IDX, ACCOUNT_PROCEDURE_SET_CODE_PROC_IDX, ACCOUNT_PROCEDURE_SET_ITEM_PROC_IDX, }; +use super::{ + mock::account::{mock_account, mock_fungible_faucet, mock_non_fungible_faucet}, + TransactionKernel, +}; pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN: u64 = 3238098370154045919; @@ -100,7 +102,7 @@ pub enum AccountSeedType { /// Returns the account id and seed for the specified account type. pub fn generate_account_seed(account_seed_type: AccountSeedType) -> (AccountId, Word) { - let assembler = assembler(); + let assembler = TransactionKernel::assembler(); let init_seed: [u8; 32] = Default::default(); let (account, account_type) = match account_seed_type { diff --git a/mock/src/lib.rs b/mock/src/lib.rs index 66f582e87..c164f5411 100644 --- a/mock/src/lib.rs +++ b/mock/src/lib.rs @@ -1,6 +1,6 @@ use std::{fs::File, io::Read, path::PathBuf}; -use miden_lib::{assembler::assembler, memory}; +use miden_lib::transaction::{memory, TransactionKernel}; use miden_objects::{ accounts::Account, notes::NoteVault, @@ -67,7 +67,7 @@ where // mock account method for testing from root context adv.insert_into_map(Word::default(), vec![Felt::new(255)]).unwrap(); - let assembler = assembler(); + let assembler = TransactionKernel::assembler(); let code = match file_path { Some(file_path) => load_file_with_code(imports, code, file_path), @@ -101,7 +101,7 @@ pub fn prepare_transaction( imports: &str, file_path: Option, ) -> PreparedTransaction { - let assembler = assembler(); + let assembler = TransactionKernel::assembler(); let code = match file_path { Some(file_path) => load_file_with_code(imports, code, file_path), diff --git a/mock/src/mock/account.rs b/mock/src/mock/account.rs index 30d7e7150..50d8c87f6 100644 --- a/mock/src/mock/account.rs +++ b/mock/src/mock/account.rs @@ -1,4 +1,4 @@ -use miden_lib::memory::FAUCET_STORAGE_DATA_SLOT; +use miden_lib::transaction::memory::FAUCET_STORAGE_DATA_SLOT; use miden_objects::{ accounts::{Account, AccountCode, AccountId, AccountStorage, AccountVault, StorageSlotType}, assembly::{Assembler, ModuleAst}, diff --git a/mock/src/mock/transaction.rs b/mock/src/mock/transaction.rs index eb06ac726..a67eb2305 100644 --- a/mock/src/mock/transaction.rs +++ b/mock/src/mock/transaction.rs @@ -1,4 +1,3 @@ -use miden_lib::assembler::assembler; use miden_objects::{ accounts::{Account, AccountDelta}, notes::Note, @@ -12,6 +11,7 @@ use miden_objects::{ use vm_processor::{AdviceInputs, Operation, Program}; use super::{ + super::TransactionKernel, account::{ mock_account, mock_fungible_faucet, mock_new_account, mock_non_fungible_faucet, MockAccountType, @@ -26,7 +26,7 @@ pub fn mock_inputs( asset_preservation: AssetPreservationStatus, ) -> (Account, BlockHeader, ChainMmr, Vec) { // Create assembler and assembler context - let assembler = assembler(); + let assembler = TransactionKernel::assembler(); // Create an account with storage items let account = match account_type { @@ -64,7 +64,7 @@ pub fn mock_inputs_with_existing( let auxiliary_data = AdviceInputs::default(); // Create assembler and assembler context - let assembler = assembler(); + let assembler = TransactionKernel::assembler(); // Create an account with storage items @@ -99,7 +99,7 @@ pub fn mock_inputs_with_existing( pub fn mock_executed_tx(asset_preservation: AssetPreservationStatus) -> ExecutedTransaction { // Create assembler and assembler context - let assembler = assembler(); + let assembler = TransactionKernel::assembler(); // Initial Account let initial_account = mock_account(None, Felt::ONE, None, &assembler); From ddfd1189bf2ab01c21e38cee20cd84f53271284a Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Fri, 29 Dec 2023 01:58:15 -0800 Subject: [PATCH 14/21] refactor: use InputNotes and OutputNotes in ProvenTransaction --- miden-lib/src/tests/test_tx.rs | 15 ++- miden-tx/src/lib.rs | 2 +- miden-tx/src/prover/mod.rs | 10 +- miden-tx/src/verifier/mod.rs | 37 +----- objects/src/notes/envelope.rs | 2 +- objects/src/transaction/executed_tx.rs | 16 +-- objects/src/transaction/inputs.rs | 88 +++++++++----- objects/src/transaction/outputs.rs | 134 ++++++++++++++++++---- objects/src/transaction/proven_tx.rs | 79 +++++++------ objects/src/transaction/transaction_id.rs | 25 +--- objects/src/transaction/tx_witness.rs | 22 ++-- 11 files changed, 256 insertions(+), 174 deletions(-) diff --git a/miden-lib/src/tests/test_tx.rs b/miden-lib/src/tests/test_tx.rs index 9e4cb3875..cfd42ed36 100644 --- a/miden-lib/src/tests/test_tx.rs +++ b/miden-lib/src/tests/test_tx.rs @@ -1,4 +1,7 @@ -use miden_objects::{notes::Note, transaction::OutputNotes}; +use miden_objects::{ + notes::Note, + transaction::{OutputNote, OutputNotes}, +}; use mock::{ constants::ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, mock::{account::MockAccountType, notes::AssetPreservationStatus, transaction::mock_inputs}, @@ -166,10 +169,12 @@ fn test_get_output_notes_hash() { .unwrap(); // compute expected output notes hash - let expected_output_notes_hash = - OutputNotes::new(vec![output_note_1.clone().into(), output_note_2.clone().into()]) - .unwrap() - .commitment(); + let expected_output_notes_hash = OutputNotes::new(vec![ + OutputNote::from(output_note_1.clone()), + OutputNote::from(output_note_2.clone()), + ]) + .unwrap() + .commitment(); let code = format!( " diff --git a/miden-tx/src/lib.rs b/miden-tx/src/lib.rs index 2d8da2e33..a552387ae 100644 --- a/miden-tx/src/lib.rs +++ b/miden-tx/src/lib.rs @@ -6,7 +6,7 @@ use miden_objects::{ transaction::{ExecutedTransaction, PreparedTransaction}, utils::collections::BTreeMap, vm::CodeBlock, - AccountError, Digest, Hasher, + AccountError, Digest, }; use vm_core::Program; use vm_processor::{ExecutionError, RecAdviceProvider}; diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 7a02eb1af..5f7ee9327 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -1,6 +1,6 @@ use miden_lib::transaction::ToTransactionKernelInputs; use miden_objects::transaction::{ - PreparedTransaction, ProvenTransaction, TransactionOutputs, TransactionWitness, + InputNotes, PreparedTransaction, ProvenTransaction, TransactionOutputs, TransactionWitness, }; use miden_prover::prove; pub use miden_prover::ProvingOptions; @@ -54,8 +54,8 @@ impl TransactionProver { tx_inputs.account.id(), tx_inputs.account.hash(), tx_outputs.account.hash(), - tx_inputs.input_notes.nullifiers().collect(), - tx_outputs.output_notes.envelopes().collect(), + tx_inputs.input_notes.into(), + tx_outputs.output_notes.into(), tx_script.map(|tx_script| *tx_script.hash()), tx_inputs.block_header.hash(), proof, @@ -103,8 +103,8 @@ impl TransactionProver { account_id, initial_account_hash, tx_outputs.account.hash(), - consumed_notes_info, - tx_outputs.output_notes.envelopes().collect(), + InputNotes::new(consumed_notes_info).unwrap(), + tx_outputs.output_notes.into(), tx_script_root, block_hash, proof, diff --git a/miden-tx/src/verifier/mod.rs b/miden-tx/src/verifier/mod.rs index 469ee7ddc..8839ab1f6 100644 --- a/miden-tx/src/verifier/mod.rs +++ b/miden-tx/src/verifier/mod.rs @@ -3,15 +3,11 @@ use core::ops::Range; use miden_lib::transaction::{ FINAL_ACCOUNT_HASH_WORD_IDX, OUTPUT_NOTES_COMMITMENT_WORD_IDX, TX_SCRIPT_ROOT_WORD_IDX, }; -use miden_objects::{ - notes::{NoteEnvelope, Nullifier}, - transaction::ProvenTransaction, - Felt, Word, WORD_SIZE, ZERO, -}; +use miden_objects::{transaction::ProvenTransaction, Felt, WORD_SIZE, ZERO}; use miden_verifier::verify; use vm_core::{stack::STACK_TOP_SIZE, ProgramInfo, StackInputs, StackOutputs}; -use super::{Digest, Hasher, TransactionCompiler, TransactionVerifierError}; +use super::{TransactionCompiler, TransactionVerifierError}; /// The [TransactionVerifier] is used to verify a [ProvenTransaction]. /// @@ -58,32 +54,10 @@ impl TransactionVerifier { // HELPERS // -------------------------------------------------------------------------------------------- - /// Returns the consumed notes commitment. - fn compute_consumed_notes_hash(consumed_notes: &[Nullifier]) -> Digest { - let mut elements: Vec = Vec::with_capacity(consumed_notes.len() * 8); - for nullifier in consumed_notes.iter() { - elements.extend_from_slice(nullifier.inner().as_elements()); - elements.extend_from_slice(&Word::default()); - } - Hasher::hash_elements(&elements) - } - - /// Returns the created notes commitment. - fn compute_created_notes_commitment(created_notes: &[NoteEnvelope]) -> Digest { - let mut elements: Vec = Vec::with_capacity(created_notes.len() * 8); - for note in created_notes.iter() { - elements.extend_from_slice(note.note_hash().as_elements()); - elements.extend_from_slice(&Word::from(note.metadata())); - } - Hasher::hash_elements(&elements) - } - /// Returns the stack inputs for the transaction. fn build_stack_inputs(transaction: &ProvenTransaction) -> StackInputs { let mut stack_inputs: Vec = Vec::with_capacity(13); - stack_inputs.extend_from_slice( - Self::compute_consumed_notes_hash(transaction.input_note_nullifiers()).as_elements(), - ); + stack_inputs.extend_from_slice(transaction.input_notes().commitment().as_elements()); stack_inputs.extend_from_slice(transaction.initial_account_hash().as_elements()); stack_inputs.push(transaction.account_id().into()); stack_inputs.extend_from_slice(transaction.block_ref().as_elements()); @@ -108,9 +82,8 @@ impl TransactionVerifier { let mut stack_outputs: Vec = vec![ZERO; STACK_TOP_SIZE]; stack_outputs[TX_SCRIPT_ROOT_RANGE] .copy_from_slice(transaction.tx_script_root().unwrap_or_default().as_elements()); - stack_outputs[CREATED_NOTES_COMMITMENT_RANGE].copy_from_slice( - Self::compute_created_notes_commitment(transaction.created_notes()).as_elements(), - ); + stack_outputs[CREATED_NOTES_COMMITMENT_RANGE] + .copy_from_slice(transaction.output_notes().commitment().as_elements()); stack_outputs[FINAL_ACCOUNT_HASH_RANGE] .copy_from_slice(transaction.final_account_hash().as_elements()); stack_outputs.reverse(); diff --git a/objects/src/notes/envelope.rs b/objects/src/notes/envelope.rs index cbb054eee..9655ba507 100644 --- a/objects/src/notes/envelope.rs +++ b/objects/src/notes/envelope.rs @@ -16,7 +16,7 @@ use super::{Digest, Felt, Note, NoteMetadata, Vec, Word}; /// - tag /// - num assets /// - ZERO -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct NoteEnvelope { note_hash: Digest, diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index f9ec7ac37..259b2db19 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -1,3 +1,5 @@ +use core::cell::OnceCell; + use super::{ AdviceInputs, InputNotes, OutputNotes, Program, TransactionId, TransactionInputs, TransactionOutputs, TransactionScript, TransactionWitness, @@ -22,7 +24,7 @@ use crate::{ /// witness). #[derive(Debug, Clone)] pub struct ExecutedTransaction { - id: TransactionId, + id: OnceCell, program: Program, tx_inputs: TransactionInputs, tx_outputs: TransactionOutputs, @@ -61,16 +63,8 @@ impl ExecutedTransaction { // if this transaction was executed against a new account, validate the account seed tx_inputs.validate_new_account_seed()?; - // build transaction ID - let id = TransactionId::new( - tx_inputs.account.hash(), - tx_outputs.account.hash(), - tx_inputs.input_notes.commitment(), - tx_outputs.output_notes.commitment(), - ); - Ok(Self { - id, + id: OnceCell::new(), program, tx_inputs, tx_outputs, @@ -85,7 +79,7 @@ impl ExecutedTransaction { /// Returns a unique identifier of this transaction. pub fn id(&self) -> TransactionId { - self.id + *self.id.get_or_init(|| self.into()) } /// Returns a reference the program defining this transaction. diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index aabdeb19c..6f6da50c9 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -1,4 +1,4 @@ -use core::cell::OnceCell; +use core::{cell::OnceCell, fmt::Debug}; use super::{BlockHeader, ChainMmr, Digest, Felt, Hasher, Word, MAX_INPUT_NOTES_PER_TRANSACTION}; use crate::{ @@ -43,19 +43,56 @@ impl TransactionInputs { } } +// TO NULLIFIER TRAIT +// ================================================================================================ + +/// Defines how a note object can be reduced to a nullifier. +/// +/// This trait is implemented on both [InputNote] and [Nullifier] so that we can treat them +/// generically as [InputNotes]. +pub trait ToNullifier: + Debug + Clone + PartialEq + Eq + Serializable + Deserializable + Sized +{ + fn nullifier(&self) -> Nullifier; +} + +impl ToNullifier for InputNote { + fn nullifier(&self) -> Nullifier { + self.note.nullifier() + } +} + +impl ToNullifier for Nullifier { + fn nullifier(&self) -> Nullifier { + *self + } +} + +impl From for InputNotes { + fn from(value: InputNotes) -> Self { + Self { + notes: value.notes.iter().map(|note| note.nullifier()).collect(), + commitment: OnceCell::new(), + } + } +} + // INPUT NOTES // ================================================================================================ -/// Contains a list of input notes for a transaction. +/// Contains a list of input notes for a transaction. The list can be empty if the transaction does +/// not consume any notes. /// -/// The list can be empty if the transaction does not consume any notes. +/// For the purposes of this struct, anything that can be reduced to a [Nullifier] can be an input +/// note. However, [ToNullifier] trait is currently implemented only for [InputNote] and [Nullifier], +/// and so these are the only two allowed input note types. #[derive(Debug, Clone)] -pub struct InputNotes { - notes: Vec, +pub struct InputNotes { + notes: Vec, commitment: OnceCell, } -impl InputNotes { +impl InputNotes { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- /// Returns new [InputNotes] instantiated from the provided vector of notes. @@ -64,7 +101,7 @@ impl InputNotes { /// 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 { + pub fn new(notes: Vec) -> Result { if notes.len() > MAX_INPUT_NOTES_PER_TRANSACTION { return Err(TransactionError::TooManyInputNotes { max: MAX_INPUT_NOTES_PER_TRANSACTION, @@ -74,8 +111,8 @@ impl InputNotes { let mut seen_notes = BTreeSet::new(); for note in notes.iter() { - if !seen_notes.insert(note.note().hash()) { - return Err(TransactionError::DuplicateInputNote(note.note().hash())); + if !seen_notes.insert(note.nullifier().inner()) { + return Err(TransactionError::DuplicateInputNote(note.nullifier().inner())); } } @@ -87,7 +124,7 @@ impl InputNotes { /// Returns a commitment to these input notes. pub fn commitment(&self) -> Digest { - *self.commitment.get_or_init(|| build_input_notes_commitment(self.nullifiers())) + *self.commitment.get_or_init(|| build_input_notes_commitment(&self.notes)) } /// Returns total number of input notes. @@ -100,8 +137,8 @@ impl InputNotes { self.notes.is_empty() } - /// Returns a reference to the [InputNote] located at the specified index. - pub fn get_note(&self, idx: usize) -> &InputNote { + /// Returns a reference to the note located at the specified index. + pub fn get_note(&self, idx: usize) -> &T { &self.notes[idx] } @@ -109,18 +146,13 @@ impl InputNotes { // -------------------------------------------------------------------------------------------- /// Returns an iterator over notes in this [InputNotes]. - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.notes.iter() } - - /// Returns an iterator over nullifiers of all notes in this [InputNotes]. - pub fn nullifiers(&self) -> impl Iterator + '_ { - self.notes.iter().map(|note| note.note().nullifier()) - } } -impl IntoIterator for InputNotes { - type Item = InputNote; +impl IntoIterator for InputNotes { + type Item = T; type IntoIter = collections::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { @@ -128,18 +160,18 @@ impl IntoIterator for InputNotes { } } -impl PartialEq for InputNotes { +impl PartialEq for InputNotes { fn eq(&self, other: &Self) -> bool { self.notes == other.notes } } -impl Eq for InputNotes {} +impl Eq for InputNotes {} // SERIALIZATION // ------------------------------------------------------------------------------------------------ -impl Serializable for InputNotes { +impl Serializable for InputNotes { fn write_into(&self, target: &mut W) { // assert is OK here because we enforce max number of notes in the constructor assert!(self.notes.len() <= u16::MAX.into()); @@ -148,10 +180,10 @@ impl Serializable for InputNotes { } } -impl Deserializable for InputNotes { +impl Deserializable for InputNotes { fn read_from(source: &mut R) -> Result { let num_notes = source.read_u16()?; - let notes = InputNote::read_batch_from(source, num_notes.into())?; + let notes = T::read_batch_from(source, num_notes.into())?; Self::new(notes).map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } @@ -163,10 +195,10 @@ impl Deserializable for InputNotes { /// /// This is a sequential hash of all (nullifier, ZERO) pairs for the notes consumed in the /// transaction. -pub fn build_input_notes_commitment>(nullifiers: I) -> Digest { +pub fn build_input_notes_commitment(notes: &[T]) -> Digest { let mut elements: Vec = Vec::new(); - for nullifier in nullifiers { - elements.extend_from_slice(nullifier.as_elements()); + for note in notes { + elements.extend_from_slice(note.nullifier().as_elements()); elements.extend_from_slice(&Word::default()); } Hasher::hash_elements(&elements) diff --git a/objects/src/transaction/outputs.rs b/objects/src/transaction/outputs.rs index b028aea16..eabfc5405 100644 --- a/objects/src/transaction/outputs.rs +++ b/objects/src/transaction/outputs.rs @@ -1,10 +1,14 @@ -use core::cell::OnceCell; +use core::{cell::OnceCell, fmt::Debug}; use super::MAX_OUTPUT_NOTES_PER_TRANSACTION; use crate::{ accounts::AccountStub, notes::{Note, NoteEnvelope, NoteMetadata, NoteVault}, - utils::collections::{self, BTreeSet, Vec}, + utils::{ + collections::{self, BTreeSet, Vec}, + serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, + string::ToString, + }, Digest, Felt, Hasher, StarkField, TransactionError, Word, }; @@ -18,17 +22,65 @@ pub struct TransactionOutputs { pub output_notes: OutputNotes, } +// TO ENVELOPE TRAIT +// ================================================================================================ + +/// Defines how a note object can be reduced to a note envelope (i.e., (hash, metadata) tuple). +/// +/// This trait is implemented on both [OutputNote] and [NoteEnvelope] so that we can treat them +/// generically as [OutputNotes]. +pub trait ToEnvelope: + Debug + Clone + PartialEq + Eq + Serializable + Deserializable + Sized +{ + fn hash(&self) -> Digest; + fn metadata(&self) -> NoteMetadata; +} + +impl ToEnvelope for OutputNote { + fn hash(&self) -> Digest { + self.hash() + } + + fn metadata(&self) -> NoteMetadata { + *self.metadata() + } +} + +impl ToEnvelope for NoteEnvelope { + fn hash(&self) -> Digest { + self.note_hash() + } + + fn metadata(&self) -> NoteMetadata { + *self.metadata() + } +} + +impl From for OutputNotes { + fn from(notes: OutputNotes) -> Self { + Self { + notes: notes.notes.iter().map(|note| note.envelope).collect(), + commitment: OnceCell::new(), + } + } +} + // OUTPUT NOTES // ================================================================================================ -/// Contains a list of output notes of a transaction. +/// Contains a list of output notes of a transaction. The list can be empty if the transaction does +/// not produce any notes. +/// +/// For the purposes of this struct, anything that can be reduced to a note envelope can be an +/// output note. However, [ToEnvelope] trait is currently implemented only for [OutputNote] and +/// [NoteEnvelope], and so these are the only two allowed output note types. #[derive(Debug, Clone)] -pub struct OutputNotes { - notes: Vec, +pub struct OutputNotes { + notes: Vec, commitment: OnceCell, } -impl OutputNotes { +impl OutputNotes { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- /// Returns new [OutputNotes] instantiated from the provide vector of notes. @@ -37,7 +89,7 @@ impl OutputNotes { /// 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 { + pub fn new(notes: Vec) -> Result { if notes.len() > MAX_OUTPUT_NOTES_PER_TRANSACTION { return Err(TransactionError::TooManyOutputNotes { max: MAX_OUTPUT_NOTES_PER_TRANSACTION, @@ -63,7 +115,7 @@ impl OutputNotes { /// 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)) + *self.commitment.get_or_init(|| build_output_notes_commitment(&self.notes)) } /// Returns total number of output notes. pub fn num_notes(&self) -> usize { @@ -75,27 +127,22 @@ impl OutputNotes { self.notes.is_empty() } - /// Returns a reference to the [OutputNote] located at the specified index. - pub fn get_note(&self, idx: usize) -> &OutputNote { + /// Returns a reference to the note located at the specified index. + pub fn get_note(&self, idx: usize) -> &T { &self.notes[idx] } // ITERATORS // -------------------------------------------------------------------------------------------- - /// Returns an iterator over notes in this [OutputNote]. - pub fn iter(&self) -> impl Iterator { + /// Returns an iterator over notes in this [OutputNotes]. + 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; +impl IntoIterator for OutputNotes { + type Item = T; type IntoIter = collections::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { @@ -103,13 +150,33 @@ impl IntoIterator for OutputNotes { } } -impl PartialEq for OutputNotes { +impl PartialEq for OutputNotes { fn eq(&self, other: &Self) -> bool { self.notes == other.notes } } -impl Eq for OutputNotes {} +impl Eq for OutputNotes {} + +// SERIALIZATION +// ------------------------------------------------------------------------------------------------ + +impl Serializable for OutputNotes { + fn write_into(&self, target: &mut W) { + // assert is OK here because we enforce max number of notes in the constructor + assert!(self.notes.len() <= u16::MAX.into()); + target.write_u16(self.notes.len() as u16); + self.notes.write_into(target); + } +} + +impl Deserializable for OutputNotes { + fn read_from(source: &mut R) -> Result { + let num_notes = source.read_u16()?; + let notes = T::read_batch_from(source, num_notes.into())?; + Self::new(notes).map_err(|err| DeserializationError::InvalidValue(err.to_string())) + } +} // HELPER FUNCTIONS // ------------------------------------------------------------------------------------------------ @@ -118,7 +185,7 @@ impl Eq for OutputNotes {} /// /// 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 { +fn build_output_notes_commitment(notes: &[T]) -> Digest { let mut elements: Vec = Vec::with_capacity(notes.len() * 8); for note in notes.iter() { elements.extend_from_slice(note.hash().as_elements()); @@ -135,7 +202,7 @@ fn build_input_notes_commitment(notes: &[OutputNote]) -> Digest { /// /// 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)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct OutputNote { envelope: NoteEnvelope, @@ -207,3 +274,24 @@ impl From<&Note> for OutputNote { Self::new(recipient, note.vault().clone(), *note.metadata()) } } + +// SERIALIZATION +// ------------------------------------------------------------------------------------------------ + +impl Serializable for OutputNote { + fn write_into(&self, target: &mut W) { + self.recipient.write_into(target); + self.vault.write_into(target); + self.envelope.metadata().write_into(target); + } +} + +impl Deserializable for OutputNote { + fn read_from(source: &mut R) -> Result { + let recipient = Digest::read_from(source)?; + let vault = NoteVault::read_from(source)?; + let metadata = NoteMetadata::read_from(source)?; + + Ok(Self::new(recipient, vault, metadata)) + } +} diff --git a/objects/src/transaction/proven_tx.rs b/objects/src/transaction/proven_tx.rs index 6f1ff10d4..d1eef255b 100644 --- a/objects/src/transaction/proven_tx.rs +++ b/objects/src/transaction/proven_tx.rs @@ -1,91 +1,100 @@ +use core::cell::OnceCell; + use miden_crypto::utils::{ByteReader, ByteWriter, Deserializable, Serializable}; use miden_verifier::ExecutionProof; use vm_processor::DeserializationError; -use super::{AccountId, Digest, NoteEnvelope, Nullifier, TransactionId, Vec}; +use super::{AccountId, Digest, InputNotes, NoteEnvelope, Nullifier, OutputNotes, TransactionId}; // PROVEN TRANSACTION // ================================================================================================ -/// Resultant object of executing and proving a transaction. It contains the minimal -/// amount of data needed to verify that the transaction was executed correctly. -/// Contains: -/// - account_id: the account that the transaction was executed against. +/// The result of executing and proving a transaction. +/// +/// This struct contains all the data required to verify that a transaction was executed correctly. +/// Specifically: +/// - account_id: ID of the account that the transaction was executed against. /// - initial_account_hash: the hash of the account before the transaction was executed. /// - final_account_hash: the hash of the account after the transaction was executed. /// - input_notes: a list of nullifier for all notes consumed by the transaction. -/// - created_notes: a list of created notes. -/// - tx_script_root: the script root of the transaction. +/// - output_notes: a list of (note_hash, metadata) tuples for all notes created by the +/// transaction. +/// - tx_script_root: the script root of the transaction, if one was used. /// - block_ref: the block hash of the last known block at the time the transaction was executed. -/// - proof: the proof of the transaction. +/// - proof: a STARK proof that attests to the correct execution of the transaction. #[derive(Clone, Debug)] pub struct ProvenTransaction { + id: OnceCell, account_id: AccountId, initial_account_hash: Digest, final_account_hash: Digest, - input_note_nullifiers: Vec, - created_notes: Vec, + input_notes: InputNotes, + output_notes: OutputNotes, tx_script_root: Option, block_ref: Digest, proof: ExecutionProof, } impl ProvenTransaction { + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + + /// Returns a new [ProvenTransaction] instantiated from the provided parameters. #[allow(clippy::too_many_arguments)] - /// Creates a new ProvenTransaction object. pub fn new( account_id: AccountId, initial_account_hash: Digest, final_account_hash: Digest, - input_note_nullifiers: Vec, - created_notes: Vec, + input_notes: InputNotes, + output_notes: OutputNotes, tx_script_root: Option, block_ref: Digest, proof: ExecutionProof, ) -> Self { Self { + id: OnceCell::new(), account_id, initial_account_hash, final_account_hash, - input_note_nullifiers, - created_notes, + input_notes, + output_notes, tx_script_root, block_ref, proof, } } - // ACCESSORS + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- /// Returns unique identifier of this transaction. pub fn id(&self) -> TransactionId { - self.into() + *self.id.get_or_init(|| self.into()) } - /// Returns the account ID. + /// Returns ID of the account against which this transaction was executed. pub fn account_id(&self) -> AccountId { self.account_id } - /// Returns the initial account hash. + /// Returns the initial account state hash. pub fn initial_account_hash(&self) -> Digest { self.initial_account_hash } - /// Returns the final account hash. + /// Returns the final account state hash. pub fn final_account_hash(&self) -> Digest { self.final_account_hash } - /// Returns the nullifiers of consumed notes. - pub fn input_note_nullifiers(&self) -> &[Nullifier] { - &self.input_note_nullifiers + /// Returns a reference to the notes consumed by the transaction. + pub fn input_notes(&self) -> &InputNotes { + &self.input_notes } - /// Returns the created notes. - pub fn created_notes(&self) -> &[NoteEnvelope] { - &self.created_notes + /// Returns a reference to the notes produced by the transaction. + pub fn output_notes(&self) -> &OutputNotes { + &self.output_notes } /// Returns the script root of the transaction. @@ -112,10 +121,8 @@ impl Serializable for ProvenTransaction { self.account_id.write_into(target); self.initial_account_hash.write_into(target); self.final_account_hash.write_into(target); - target.write_u64(self.input_note_nullifiers.len() as u64); - self.input_note_nullifiers.write_into(target); - target.write_u64(self.created_notes.len() as u64); - self.created_notes.write_into(target); + self.input_notes.write_into(target); + self.output_notes.write_into(target); self.tx_script_root.write_into(target); self.block_ref.write_into(target); self.proof.write_into(target); @@ -128,11 +135,8 @@ impl Deserializable for ProvenTransaction { let initial_account_hash = Digest::read_from(source)?; let final_account_hash = Digest::read_from(source)?; - let num_input_notes = source.read_u64()?; - let input_notes = Nullifier::read_batch_from(source, num_input_notes as usize)?; - - let num_output_notes = source.read_u64()?; - let created_notes = NoteEnvelope::read_batch_from(source, num_output_notes as usize)?; + let input_notes = InputNotes::::read_from(source)?; + let output_notes = OutputNotes::::read_from(source)?; let tx_script_root = Deserializable::read_from(source)?; @@ -140,11 +144,12 @@ impl Deserializable for ProvenTransaction { let proof = ExecutionProof::read_from(source)?; Ok(Self { + id: OnceCell::new(), account_id, initial_account_hash, final_account_hash, - input_note_nullifiers: input_notes, - created_notes, + input_notes, + output_notes, tx_script_root, block_ref, proof, diff --git a/objects/src/transaction/transaction_id.rs b/objects/src/transaction/transaction_id.rs index 4511e0033..96ebe56f4 100644 --- a/objects/src/transaction/transaction_id.rs +++ b/objects/src/transaction/transaction_id.rs @@ -1,6 +1,4 @@ -use super::{ - Digest, ExecutedTransaction, Felt, Hasher, ProvenTransaction, Vec, Word, WORD_SIZE, ZERO, -}; +use super::{Digest, ExecutedTransaction, Felt, Hasher, ProvenTransaction, Word, WORD_SIZE, ZERO}; use crate::utils::serde::{ ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, }; @@ -52,28 +50,11 @@ impl TransactionId { impl From<&ProvenTransaction> for TransactionId { fn from(tx: &ProvenTransaction) -> Self { - // TODO: move input/output note hash computations into a more central location - let input_notes_hash = { - let mut elements: Vec = Vec::with_capacity(tx.input_note_nullifiers().len() * 8); - for nullifier in tx.input_note_nullifiers().iter() { - elements.extend_from_slice(nullifier.as_elements()); - elements.extend_from_slice(&Word::default()); - } - Hasher::hash_elements(&elements) - }; - let output_notes_hash = { - let mut elements: Vec = Vec::with_capacity(tx.created_notes().len() * 8); - for note in tx.created_notes().iter() { - elements.extend_from_slice(note.note_hash().as_elements()); - elements.extend_from_slice(&Word::from(note.metadata())); - } - Hasher::hash_elements(&elements) - }; Self::new( tx.initial_account_hash(), tx.final_account_hash(), - input_notes_hash, - output_notes_hash, + tx.input_notes().commitment(), + tx.output_notes().commitment(), ) } } diff --git a/objects/src/transaction/tx_witness.rs b/objects/src/transaction/tx_witness.rs index 3a8d53385..622c9cdc3 100644 --- a/objects/src/transaction/tx_witness.rs +++ b/objects/src/transaction/tx_witness.rs @@ -29,6 +29,8 @@ pub struct TransactionWitness { } impl TransactionWitness { + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- /// Creates a new [TransactionWitness] from the provided data. pub fn new( account_id: AccountId, @@ -50,14 +52,21 @@ impl TransactionWitness { } } + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + /// Returns the account id of the account the transaction is executed against. - pub fn account_id(&self) -> &AccountId { - &self.account_id + pub fn account_id(&self) -> AccountId { + self.account_id } /// Returns the initial account hash of the account the transaction is executed against. - pub fn initial_account_hash(&self) -> &Digest { - &self.initial_account_hash + pub fn initial_account_hash(&self) -> Digest { + self.initial_account_hash + } + /// Returns a commitment to the notes consumed by the transaction. + pub fn input_notes_hash(&self) -> Digest { + self.input_notes_hash } /// Returns the block hash of the latest known block. @@ -65,11 +74,6 @@ impl TransactionWitness { &self.block_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. /// /// # Errors From 7913c4ba571ef2e90e63d201c943d5417d5d129f Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Sat, 30 Dec 2023 02:32:46 -0800 Subject: [PATCH 15/21] refactor: move transaction output parsing to miden-lib --- miden-lib/src/transaction/inputs.rs | 114 ++++++------- miden-lib/src/transaction/mod.rs | 225 +++++++++++++++++++++++++- miden-lib/src/transaction/outputs.rs | 39 +---- miden-tx/src/error.rs | 18 --- miden-tx/src/executor/mod.rs | 65 +++++--- miden-tx/src/lib.rs | 7 +- miden-tx/src/prover/mod.rs | 52 +++--- miden-tx/src/result.rs | 121 -------------- miden-tx/src/tests.rs | 24 ++- objects/src/errors.rs | 1 + objects/src/lib.rs | 4 +- objects/src/transaction/inputs.rs | 9 ++ objects/src/transaction/mod.rs | 5 +- objects/src/transaction/tx_witness.rs | 115 ++----------- 14 files changed, 387 insertions(+), 412 deletions(-) delete mode 100644 miden-tx/src/result.rs diff --git a/miden-lib/src/transaction/inputs.rs b/miden-lib/src/transaction/inputs.rs index eb8e83911..29478c24a 100644 --- a/miden-lib/src/transaction/inputs.rs +++ b/miden-lib/src/transaction/inputs.rs @@ -2,13 +2,15 @@ use miden_objects::{ accounts::Account, transaction::{ ChainMmr, ExecutedTransaction, InputNotes, PreparedTransaction, TransactionInputs, - TransactionScript, + TransactionScript, TransactionWitness, }, utils::{collections::Vec, vec, IntoBytes}, vm::{AdviceInputs, StackInputs}, - Digest, Felt, Word, ZERO, + Felt, Word, ZERO, }; +use super::TransactionKernel; + // TRANSACTION KERNEL INPUTS // ================================================================================================ @@ -20,7 +22,13 @@ pub trait ToTransactionKernelInputs { impl ToTransactionKernelInputs for PreparedTransaction { fn get_kernel_inputs(&self) -> (StackInputs, AdviceInputs) { - let stack_inputs = build_stack_inputs(self.tx_inputs()); + let account = self.account(); + let stack_inputs = TransactionKernel::build_input_stack( + account.id(), + if account.is_new() { None } else { Some(account.hash()) }, + self.input_notes().commitment(), + self.block_header().hash(), + ); let advice_inputs = build_advice_inputs(self.tx_inputs(), self.tx_script()); (stack_inputs, advice_inputs) } @@ -28,39 +36,29 @@ impl ToTransactionKernelInputs for PreparedTransaction { impl ToTransactionKernelInputs for ExecutedTransaction { fn get_kernel_inputs(&self) -> (StackInputs, AdviceInputs) { - let stack_inputs = build_stack_inputs(self.tx_inputs()); + let account = self.initial_account(); + let stack_inputs = TransactionKernel::build_input_stack( + account.id(), + if account.is_new() { None } else { Some(account.hash()) }, + self.input_notes().commitment(), + self.block_header().hash(), + ); let advice_inputs = build_advice_inputs(self.tx_inputs(), self.tx_script()); (stack_inputs, advice_inputs) } } -// STACK INPUTS -// ================================================================================================ +impl ToTransactionKernelInputs for TransactionWitness { + fn get_kernel_inputs(&self) -> (StackInputs, AdviceInputs) { + let stack_inputs = TransactionKernel::build_input_stack( + self.account_id(), + Some(self.initial_account_hash()), // TODO + self.input_notes_hash(), + self.block_hash(), + ); -/// Returns the input stack required for executing a transaction with the specified inputs. -/// -/// This includes the input notes commitment, the account hash, the account id, and the block hash. -/// -/// Stack: [BH, acct_id, IAH, NC] -/// -/// - BH is the latest known block hash at the time of transaction execution. -/// - acct_id is the account id of the account that the transaction is being executed against. -/// - IAH is the initial account hash of the account that the transaction is being executed against. -/// - NC is the nullifier commitment of the transaction. This is a sequential hash of all -/// (nullifier, ZERO) tuples for the notes consumed in the transaction. -fn build_stack_inputs(tx_inputs: &TransactionInputs) -> StackInputs { - let initial_acct_hash = if tx_inputs.account.is_new() { - Digest::default() - } else { - tx_inputs.account.hash() - }; - - let mut inputs: Vec = Vec::with_capacity(13); - inputs.extend(tx_inputs.input_notes.commitment()); - inputs.extend_from_slice(initial_acct_hash.as_elements()); - inputs.push((tx_inputs.account.id()).into()); - inputs.extend_from_slice(tx_inputs.block_header.hash().as_elements()); - StackInputs::new(inputs) + (stack_inputs, self.advice_inputs().clone()) + } } // ADVICE INPUTS @@ -81,10 +79,9 @@ fn build_advice_inputs( // build the advice map and Merkle store for relevant components add_chain_mmr_to_advice_inputs(&tx_inputs.block_chain, &mut advice_inputs); - add_account_to_advice_inputs(&tx_inputs.account, &mut advice_inputs); + add_account_to_advice_inputs(&tx_inputs.account, tx_inputs.account_seed, &mut advice_inputs); add_input_notes_to_advice_inputs(&tx_inputs.input_notes, &mut advice_inputs); add_tx_script_inputs_to_advice_map(tx_script, &mut advice_inputs); - add_account_seed_to_advice_map(tx_inputs, &mut advice_inputs); advice_inputs } @@ -144,6 +141,9 @@ fn build_advice_stack( } } +// CHAIN MMR INJECTOR +// ------------------------------------------------------------------------------------------------ + /// Inserts the chain MMR data into the provided advice inputs. /// /// Inserts the following items into the Merkle store: @@ -167,6 +167,9 @@ fn add_chain_mmr_to_advice_inputs(mmr: &ChainMmr, inputs: &mut AdviceInputs) { inputs.extend_map([(peaks.hash_peaks().into(), elements)]); } +// ACCOUNT DATA INJECTOR +// ------------------------------------------------------------------------------------------------ + /// Inserts core account data into the provided advice inputs. /// /// Inserts the following items into the Merkle store: @@ -178,7 +181,12 @@ fn add_chain_mmr_to_advice_inputs(mmr: &ChainMmr, inputs: &mut AdviceInputs) { /// - The storage types commitment |-> storage slot types vector. /// - The account procedure root |-> procedure index, for each account procedure. /// - The node |-> (key, value), for all leaf nodes of the asset vault TSMT. -fn add_account_to_advice_inputs(account: &Account, inputs: &mut AdviceInputs) { +/// - [account_id, 0, 0, 0] |-> account_seed, when account seed is provided. +fn add_account_to_advice_inputs( + account: &Account, + account_seed: Option, + inputs: &mut AdviceInputs, +) { // --- account storage ---------------------------------------------------- let storage = account.storage(); @@ -218,8 +226,19 @@ fn add_account_to_advice_inputs(account: &Account, inputs: &mut AdviceInputs) { .leaves() .map(|(idx, leaf)| (leaf.into_bytes(), vec![idx.into()])), ); + + // --- account seed ------------------------------------------------------- + if let Some(account_seed) = account_seed { + inputs.extend_map(vec![( + [account.id().into(), ZERO, ZERO, ZERO].into_bytes(), + account_seed.to_vec(), + )]); + } } +// INPUT NOTE INJECTOR +// ------------------------------------------------------------------------------------------------ + /// Populates the advice inputs for all input notes. /// /// For each note the authentication path is populated into the Merkle store, the note inputs @@ -249,7 +268,7 @@ fn add_account_to_advice_inputs(account: &Account, inputs: &mut AdviceInputs) { /// Inserts the following entries into the advice map: /// - inputs_hash |-> inputs /// - vault_hash |-> assets -/// - note_hash |-> combined note data +/// - notes_hash |-> combined note data fn add_input_notes_to_advice_inputs(notes: &InputNotes, inputs: &mut AdviceInputs) { let mut note_data: Vec = Vec::new(); @@ -272,24 +291,16 @@ fn add_input_notes_to_advice_inputs(notes: &InputNotes, inputs: &mut AdviceInput ); // add the note elements to the combined vector of note data - note_data.extend(note.serial_num()); - note_data.extend(*note.script().hash()); - note_data.extend(*note.inputs().hash()); - note_data.extend(*note.vault().hash()); - note_data.extend(Word::from(note.metadata())); - - note_data.extend(note.vault().to_padded_assets()); - - note_data.push(proof.origin().block_num.into()); - note_data.extend(*proof.sub_hash()); - note_data.extend(*proof.note_root()); - note_data.push(proof.origin().node_index.value().into()); + TransactionKernel::write_input_note_into(input_note, &mut note_data); } // insert the combined note data into the advice map inputs.extend_map([(notes.commitment().into(), note_data)]); } +// TRANSACTION SCRIPT INJECTOR +// ------------------------------------------------------------------------------------------------ + /// Inserts the following entries into the advice map: /// - input_hash |-> input, for each tx_script input fn add_tx_script_inputs_to_advice_map( @@ -302,14 +313,3 @@ fn add_tx_script_inputs_to_advice_map( ); } } - -/// Inserts the following entries into the advice map: -/// - [account_id, 0, 0, 0] |-> account_seed -fn add_account_seed_to_advice_map(tx_inputs: &TransactionInputs, inputs: &mut AdviceInputs) { - if let Some(account_seed) = tx_inputs.account_seed { - inputs.extend_map(vec![( - [tx_inputs.account.id().into(), ZERO, ZERO, ZERO].into_bytes(), - account_seed.to_vec(), - )]); - } -} diff --git a/miden-lib/src/transaction/mod.rs b/miden-lib/src/transaction/mod.rs index f2ffb96d5..16ed984f4 100644 --- a/miden-lib/src/transaction/mod.rs +++ b/miden-lib/src/transaction/mod.rs @@ -1,4 +1,15 @@ use assembly::{ast::ProgramAst, utils::DeserializationError, Assembler}; +use miden_objects::{ + accounts::AccountId, + notes::Nullifier, + transaction::{InputNote, OutputNotes, TransactionOutputs}, + utils::{ + collections::{BTreeMap, Vec}, + group_slice_elements, + }, + vm::{StackInputs, StackOutputs}, + Digest, Felt, Hasher, StarkField, TransactionError, TransactionResultError, Word, WORD_SIZE, +}; use miden_stdlib::StdLibrary; use super::MidenLib; @@ -10,8 +21,8 @@ pub use inputs::ToTransactionKernelInputs; mod outputs; pub use outputs::{ - extract_account_storage_delta, notes_try_from_elements, parse_final_account_stub, - FINAL_ACCOUNT_HASH_WORD_IDX, OUTPUT_NOTES_COMMITMENT_WORD_IDX, TX_SCRIPT_ROOT_WORD_IDX, + notes_try_from_elements, parse_final_account_stub, FINAL_ACCOUNT_HASH_WORD_IDX, + OUTPUT_NOTES_COMMITMENT_WORD_IDX, TX_SCRIPT_ROOT_WORD_IDX, }; // TRANSACTION KERNEL @@ -51,4 +62,214 @@ impl TransactionKernel { .with_kernel(Self::kernel()) .expect("kernel is well formed") } + + // INPUT / OUTPUT STACK BUILDERS + // -------------------------------------------------------------------------------------------- + + /// Returns the input stack required to execute the transaction kernel. + /// + /// This includes the input notes commitment, the account hash, the account id, and the block + /// hash. + /// + /// Stack: [BH, acct_id, IAH, NC] + /// + /// Where: + /// - BH is the latest known block hash at the time of transaction execution. + /// - acct_id is the account id of the account that the transaction is being executed against. + /// - IAH is the hash of account state immediately before the transaction is executed. For + /// newly created accounts, initial state hash is provided as [ZERO; 4]. + /// - NC is a commitment to the input notes. This is a sequential hash of all (nullifier, ZERO) + /// tuples for the notes consumed by the transaction. + pub fn build_input_stack( + acct_id: AccountId, + init_acct_hash: Option, + input_notes_hash: Digest, + block_hash: Digest, + ) -> StackInputs { + let mut inputs: Vec = Vec::with_capacity(13); + inputs.extend(input_notes_hash); + inputs.extend_from_slice(init_acct_hash.unwrap_or_default().as_elements()); + inputs.push(acct_id.into()); + inputs.extend_from_slice(block_hash.as_elements()); + StackInputs::new(inputs) + } + + /// TODO: finish description + /// + /// Stack: [TXSR, CNC, FAH] + /// + /// Where: + /// - TXSR is the transaction script root. + /// - CNC is the commitment to the notes created by the transaction. + /// - FAH is the final account hash of the account that the transaction is being + /// executed against. + pub fn parse_output_stack(stack: &StackOutputs) -> (Digest, Digest, Digest) { + // TODO: use constants + let tx_script_root = stack.get_stack_word(0).expect("msg").into(); + let output_notes_hash = stack.get_stack_word(4).expect("msg").into(); + let final_account_hash = stack.get_stack_word(8).expect("msg").into(); + + (final_account_hash, output_notes_hash, tx_script_root) + } + + // ADVICE MAP EXTRACTORS + // -------------------------------------------------------------------------------------------- + + /// TODO: add comments + pub fn parse_outputs( + stack: &StackOutputs, + adv_map: &AdviceMap, + ) -> Result { + let (final_acct_hash, output_notes_hash, _tx_script_root) = Self::parse_output_stack(stack); + + // --- parse final account state -------------------------------------- + let final_account_data: &[Word] = group_slice_elements( + adv_map + .get(final_acct_hash) + .ok_or(TransactionResultError::FinalAccountDataNotFound)?, + ); + let account = parse_final_account_stub(final_account_data) + .map_err(TransactionResultError::FinalAccountStubDataInvalid)?; + + // --- parse output notes --------------------------------------------- + + let output_notes_data: &[Word] = group_slice_elements( + adv_map + .get(output_notes_hash) + .ok_or(TransactionResultError::OutputNoteDataNotFound)?, + ); + + let mut output_notes = Vec::new(); + let mut output_note_ptr = 0; + while output_note_ptr < output_notes_data.len() { + let output_note = notes_try_from_elements(&output_notes_data[output_note_ptr..]) + .map_err(TransactionResultError::OutputNoteDataInvalid)?; + output_notes.push(output_note); + output_note_ptr += memory::NOTE_MEM_SIZE as usize; + } + + let output_notes = + OutputNotes::new(output_notes).map_err(TransactionResultError::OutputNotesError)?; + if output_notes_hash != output_notes.commitment() { + return Err(TransactionResultError::OutputNotesCommitmentInconsistent( + output_notes_hash, + output_notes.commitment(), + )); + } + + Ok(TransactionOutputs { account, output_notes }) + } + + // NOTE DATA BUILDER + // -------------------------------------------------------------------------------------------- + + /// TODO + pub fn write_input_note_into(input_note: &InputNote, target: &mut Vec) { + let note = input_note.note(); + let proof = input_note.proof(); + + // write the note info; 20 elements + target.extend(note.serial_num()); + target.extend(*note.script().hash()); + target.extend(*note.inputs().hash()); + target.extend(*note.vault().hash()); + target.extend(Word::from(note.metadata())); + + // write asset vault; 4 * num_assets elements + target.extend(note.vault().to_padded_assets()); + + // write note location info; 10 elements + target.push(proof.origin().block_num.into()); + target.extend(*proof.sub_hash()); + target.extend(*proof.note_root()); + target.push(proof.origin().node_index.value().into()); + } + + /// Returns a vectors of nullifiers read from the provided note data stream. + /// + /// Notes are expected to be arranged in the stream as follows: + /// + /// [n, note_1_data, ... note_n_data] + /// + /// where n is the number of notes in the stream. Each note is expected to be arranged as + /// follows: + /// + /// [serial_num, script_hash, input_hash, vault_hash, metadata, asset_1 ... asset_k, + /// block_num, sub_hash, notes_root, note_index] + /// + /// Thus, the number of elements + /// + /// # Errors + /// Returns an error if: + /// - The stream does not contain at least one note. + /// - The stream does not have enough data to read the specified number of notes. + /// - The stream is not fully consumed after all notes have been processed. + pub fn read_input_nullifiers_from(source: &[Felt]) -> Result, TransactionError> { + // extract the notes from the first fetch and instantiate a vector to hold nullifiers + let num_notes = source[0].as_int(); + let mut nullifiers = Vec::with_capacity(num_notes as usize); + + // iterate over the notes and extract the nullifier and script root + let mut note_ptr = 1; + while note_ptr < source.len() { + // make sure there is enough data to read (note data is well formed) + if note_ptr + 5 * WORD_SIZE > source.len() { + return Err(TransactionError::InvalidInputNoteDataLength); + } + + // compute the nullifier and extract script root and number of assets + let (nullifier, num_assets) = extract_note_data(&source[note_ptr..]); + + // push the [ConsumedNoteInfo] to the vector + nullifiers.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; + + // increment note pointer + note_ptr += (num_assets as usize * WORD_SIZE) + 30; + } + + Ok(nullifiers) + } +} + +// HELPERS +// ================================================================================================ + +/// Extracts and returns the nullifier and the number of assets from the provided note data. +/// +/// Expects the note data to be organized as follows: +/// [CN_SN, CN_SR, CN_IR, CN_VR, CN_M] +/// +/// - CN_SN is the serial number of the consumed note. +/// - CN_SR is the script root of the consumed note. +/// - CN_IR is the inputs root of the consumed note. +/// - CN_VR is the vault root of the consumed note. +/// - CN1_M is the metadata of the consumed note. +fn extract_note_data(note_data: &[Felt]) -> (Digest, u64) { + // compute the nullifier + let nullifier = Hasher::hash_elements(¬e_data[..4 * WORD_SIZE]); + + // extract the number of assets + let num_assets = note_data[4 * WORD_SIZE].as_int(); + + (nullifier, num_assets) +} + +// ADVICE MAP +// ================================================================================================ + +pub struct AdviceMap(BTreeMap<[u8; 32], Vec>); + +impl AdviceMap { + pub fn get(&self, key: Digest) -> Option<&Vec> { + self.0.get(&key.as_bytes()) + } +} + +impl From>> for AdviceMap { + fn from(value: BTreeMap<[u8; 32], Vec>) -> Self { + Self(value) + } } diff --git a/miden-lib/src/transaction/outputs.rs b/miden-lib/src/transaction/outputs.rs index e13a89019..e9188fb7c 100644 --- a/miden-lib/src/transaction/outputs.rs +++ b/miden-lib/src/transaction/outputs.rs @@ -1,9 +1,8 @@ use miden_objects::{ - accounts::{Account, AccountId, AccountStorage, AccountStorageDelta, AccountStub}, - crypto::merkle::{merkle_tree_delta, MerkleStore}, + accounts::{AccountId, AccountStub}, notes::{NoteMetadata, NoteVault}, transaction::OutputNote, - AccountError, Digest, NoteError, StarkField, TransactionResultError, Word, WORD_SIZE, + AccountError, Digest, NoteError, StarkField, Word, WORD_SIZE, }; use super::memory::{ @@ -44,40 +43,6 @@ pub fn parse_final_account_stub(elements: &[Word]) -> Result Result { - // extract storage slots delta - let tree_delta = merkle_tree_delta( - initial_account.storage().root(), - final_account_stub.storage_root(), - AccountStorage::STORAGE_TREE_DEPTH, - store, - ) - .map_err(TransactionResultError::ExtractAccountStorageSlotsDeltaFailed)?; - - // map tree delta to cleared/updated slots; we can cast indexes to u8 because the - // the number of storage slots cannot be greater than 256 - let cleared_items = tree_delta.cleared_slots().iter().map(|idx| *idx as u8).collect(); - let updated_items = tree_delta - .updated_slots() - .iter() - .map(|(idx, value)| (*idx as u8, *value)) - .collect(); - - // construct storage delta - let storage_delta = AccountStorageDelta { cleared_items, updated_items }; - - Ok(storage_delta) -} - // NOTES EXTRACTOR // ================================================================================================ diff --git a/miden-tx/src/error.rs b/miden-tx/src/error.rs index 1cebaf68b..f6cafb32e 100644 --- a/miden-tx/src/error.rs +++ b/miden-tx/src/error.rs @@ -8,24 +8,6 @@ use miden_verifier::VerificationError; use super::{AccountError, AccountId, Digest, ExecutionError}; -// TRANSACTION ERROR -// ================================================================================================ -#[derive(Debug)] -pub enum TransactionError { - TransactionCompilerError(TransactionCompilerError), - TransactionExecutorError(TransactionExecutorError), - DataStoreError(DataStoreError), -} - -impl fmt::Display for TransactionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for TransactionError {} - // TRANSACTION COMPILER ERROR // ================================================================================================ #[derive(Debug)] diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index 969f0bb4e..57cf61c6e 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -1,13 +1,12 @@ -use miden_lib::transaction::{ - extract_account_storage_delta, ToTransactionKernelInputs, TX_SCRIPT_ROOT_WORD_IDX, -}; +use miden_lib::transaction::{ToTransactionKernelInputs, TransactionKernel}; use miden_objects::{ - accounts::AccountDelta, + accounts::{Account, AccountDelta, AccountStorage, AccountStorageDelta, AccountStub}, assembly::ProgramAst, - transaction::{TransactionInputs, TransactionOutputs, TransactionScript}, - Felt, Word, WORD_SIZE, + crypto::merkle::{merkle_tree_delta, MerkleStore}, + transaction::{TransactionInputs, TransactionScript}, + vm::{Program, StackOutputs}, + Felt, TransactionResultError, Word, }; -use vm_core::{Program, StackOutputs, StarkField}; use vm_processor::ExecutionOptions; use super::{ @@ -15,7 +14,7 @@ use super::{ PreparedTransaction, RecAdviceProvider, ScriptTarget, TransactionCompiler, TransactionExecutorError, TransactionHost, }; -use crate::{host::EventHandler, TryFromVmResult}; +use crate::host::EventHandler; // TRANSACTION EXECUTOR // ================================================================================================ @@ -207,7 +206,7 @@ impl TransactionExecutor { // ================================================================================================ /// Creates a new [ExecutedTransaction] from the provided data, advice provider and stack outputs. -pub fn build_executed_transaction( +fn build_executed_transaction( program: Program, tx_script: Option, tx_inputs: TransactionInputs, @@ -216,26 +215,17 @@ pub fn build_executed_transaction( event_handler: EventHandler, ) -> Result { // finalize the advice recorder - let (advice_witness, stack, map, store) = advice_provider.finalize(); + let (advice_witness, _, map, store) = advice_provider.finalize(); // parse transaction results - let tx_outputs = TransactionOutputs::try_from_vm_result(&stack_outputs, &stack, &map, &store) + let tx_outputs = TransactionKernel::parse_outputs(&stack_outputs, &map.into()) .map_err(TransactionExecutorError::TransactionResultError)?; let final_account = &tx_outputs.account; - // assert the tx_script_root is consistent with the output stack - debug_assert_eq!( - (*tx_script.clone().map(|s| *s.hash()).unwrap_or_default()) - .into_iter() - .rev() - .map(|x| x.as_int()) - .collect::>(), - stack_outputs.stack() - [TX_SCRIPT_ROOT_WORD_IDX * WORD_SIZE..(TX_SCRIPT_ROOT_WORD_IDX + 1) * WORD_SIZE] - ); - let initial_account = &tx_inputs.account; + // build account delta + // TODO: Fix delta extraction for new account creation // extract the account storage delta let storage_delta = extract_account_storage_delta(&store, initial_account, final_account) @@ -265,3 +255,34 @@ pub fn build_executed_transaction( ) .map_err(TransactionExecutorError::ExecutedTransactionConstructionFailed) } + +/// Extracts account storage delta between the `initial_account` and `final_account_stub` from the +/// provided `MerkleStore` +fn extract_account_storage_delta( + store: &MerkleStore, + initial_account: &Account, + final_account_stub: &AccountStub, +) -> Result { + // extract storage slots delta + let tree_delta = merkle_tree_delta( + initial_account.storage().root(), + final_account_stub.storage_root(), + AccountStorage::STORAGE_TREE_DEPTH, + store, + ) + .map_err(TransactionResultError::ExtractAccountStorageSlotsDeltaFailed)?; + + // map tree delta to cleared/updated slots; we can cast indexes to u8 because the + // the number of storage slots cannot be greater than 256 + let cleared_items = tree_delta.cleared_slots().iter().map(|idx| *idx as u8).collect(); + let updated_items = tree_delta + .updated_slots() + .iter() + .map(|(idx, value)| (*idx as u8, *value)) + .collect(); + + // construct storage delta + let storage_delta = AccountStorageDelta { cleared_items, updated_items }; + + Ok(storage_delta) +} diff --git a/miden-tx/src/lib.rs b/miden-tx/src/lib.rs index a552387ae..774d5ab02 100644 --- a/miden-tx/src/lib.rs +++ b/miden-tx/src/lib.rs @@ -26,16 +26,13 @@ pub use host::TransactionHost; mod prover; pub use prover::{ProvingOptions, TransactionProver}; -mod result; -pub use result::TryFromVmResult; - mod verifier; pub use verifier::TransactionVerifier; mod error; pub use error::{ - DataStoreError, TransactionCompilerError, TransactionError, TransactionExecutorError, - TransactionProverError, TransactionVerifierError, + DataStoreError, TransactionCompilerError, TransactionExecutorError, TransactionProverError, + TransactionVerifierError, }; #[cfg(test)] diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 5f7ee9327..543d6df22 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -1,13 +1,12 @@ -use miden_lib::transaction::ToTransactionKernelInputs; +use miden_lib::transaction::{ToTransactionKernelInputs, TransactionKernel}; use miden_objects::transaction::{ - InputNotes, PreparedTransaction, ProvenTransaction, TransactionOutputs, TransactionWitness, + InputNotes, PreparedTransaction, ProvenTransaction, TransactionWitness, }; use miden_prover::prove; pub use miden_prover::ProvingOptions; use vm_processor::MemAdviceProvider; use super::{TransactionHost, TransactionProverError}; -use crate::TryFromVmResult; /// The [TransactionProver] is a stateless component which is responsible for proving transactions. /// @@ -38,14 +37,15 @@ impl TransactionProver { let advice_provider: MemAdviceProvider = advice_inputs.into(); let mut host = TransactionHost::new(advice_provider); - let (outputs, proof) = + let (stack_outputs, proof) = prove(transaction.program(), stack_inputs, &mut host, self.proof_options.clone()) .map_err(TransactionProverError::ProveTransactionProgramFailed)?; // extract transaction outputs and process transaction data let (advice_provider, _event_handler) = host.into_parts(); - let (stack, map, store) = advice_provider.into_parts(); - let tx_outputs = TransactionOutputs::try_from_vm_result(&outputs, &stack, &map, &store) + let (_, map, _) = advice_provider.into_parts(); + let adv_map = map.into(); + let tx_outputs = TransactionKernel::parse_outputs(&stack_outputs, &adv_map) .map_err(TransactionProverError::TransactionResultError)?; let (_tx_program, tx_script, tx_inputs) = transaction.into_parts(); @@ -73,37 +73,39 @@ impl TransactionProver { tx_witness: TransactionWitness, ) -> Result { // extract required data from the transaction witness - let stack_inputs = tx_witness.get_stack_inputs(); - let consumed_notes_info = tx_witness - .input_notes_info() - .map_err(TransactionProverError::CorruptTransactionWitnessConsumedNoteData)?; - let ( - account_id, - initial_account_hash, - block_hash, - _consumed_notes_hash, - tx_script_root, - tx_program, - advice_witness, - ) = tx_witness.into_parts(); + let (stack_inputs, advice_inputs) = tx_witness.get_kernel_inputs(); - let advice_provider: MemAdviceProvider = advice_witness.into(); + let input_notes = match tx_witness.input_note_data() { + Some(input_note_data) => { + let nullifiers = + TransactionKernel::read_input_nullifiers_from(input_note_data).unwrap(); + InputNotes::new(nullifiers).unwrap() + }, + None => InputNotes::default(), + }; + + let account_id = tx_witness.account_id(); + let initial_account_hash = tx_witness.initial_account_hash(); + let block_hash = tx_witness.block_hash(); + let tx_script_root = tx_witness.tx_script_root(); + + let advice_provider: MemAdviceProvider = advice_inputs.into(); let mut host = TransactionHost::new(advice_provider); - let (outputs, proof) = - prove(&tx_program, stack_inputs, &mut host, self.proof_options.clone()) + let (stack_outputs, proof) = + prove(tx_witness.program(), stack_inputs, &mut host, self.proof_options.clone()) .map_err(TransactionProverError::ProveTransactionProgramFailed)?; // extract transaction outputs and process transaction data let (advice_provider, _event_handler) = host.into_parts(); - let (stack, map, store) = advice_provider.into_parts(); - let tx_outputs = TransactionOutputs::try_from_vm_result(&outputs, &stack, &map, &store) + let (_, map, _) = advice_provider.into_parts(); + let tx_outputs = TransactionKernel::parse_outputs(&stack_outputs, &map.into()) .map_err(TransactionProverError::TransactionResultError)?; Ok(ProvenTransaction::new( account_id, initial_account_hash, tx_outputs.account.hash(), - InputNotes::new(consumed_notes_info).unwrap(), + input_notes, tx_outputs.output_notes.into(), tx_script_root, block_hash, diff --git a/miden-tx/src/result.rs b/miden-tx/src/result.rs deleted file mode 100644 index ba03ac584..000000000 --- a/miden-tx/src/result.rs +++ /dev/null @@ -1,121 +0,0 @@ -use miden_lib::transaction::{ - memory::NOTE_MEM_SIZE, notes_try_from_elements, parse_final_account_stub, - FINAL_ACCOUNT_HASH_WORD_IDX, OUTPUT_NOTES_COMMITMENT_WORD_IDX, -}; -use miden_objects::{ - accounts::AccountStub, - crypto::merkle::MerkleStore, - transaction::{OutputNote, OutputNotes, TransactionOutputs}, - utils::collections::{BTreeMap, Vec}, - Digest, Felt, TransactionResultError, Word, WORD_SIZE, -}; -use vm_core::utils::group_slice_elements; -use vm_processor::StackOutputs; - -/// A trait that defines the interface for extracting objects from the result of a VM execution. -pub trait TryFromVmResult: Sized { - type Error; - - /// Tries to create an object from the provided stack outputs and advice provider components. - fn try_from_vm_result( - stack_outputs: &StackOutputs, - advice_stack: &[Felt], - advice_map: &BTreeMap<[u8; 32], Vec>, - merkle_store: &MerkleStore, - ) -> Result; -} - -impl TryFromVmResult for TransactionOutputs { - type Error = TransactionResultError; - - fn try_from_vm_result( - stack_outputs: &StackOutputs, - advice_stack: &[Felt], - advice_map: &BTreeMap<[u8; 32], Vec>, - merkle_store: &MerkleStore, - ) -> Result { - let account = extract_final_account_stub(stack_outputs, advice_map)?; - let output_notes = - OutputNotes::try_from_vm_result(stack_outputs, advice_stack, advice_map, merkle_store)?; - - Ok(Self { account, output_notes }) - } -} - -impl TryFromVmResult for OutputNotes { - type Error = TransactionResultError; - - fn try_from_vm_result( - stack_outputs: &StackOutputs, - _advice_stack: &[Felt], - advice_map: &BTreeMap<[u8; 32], Vec>, - _merkle_store: &MerkleStore, - ) -> Result { - let created_notes_commitment: Word = stack_outputs.stack()[OUTPUT_NOTES_COMMITMENT_WORD_IDX - * WORD_SIZE - ..(OUTPUT_NOTES_COMMITMENT_WORD_IDX + 1) * WORD_SIZE] - .iter() - .rev() - .map(|x| Felt::from(*x)) - .collect::>() - .try_into() - .expect("word size is correct"); - let created_notes_commitment: Digest = created_notes_commitment.into(); - - let created_notes_data = group_slice_elements::( - advice_map - .get(&created_notes_commitment.as_bytes()) - .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: OutputNote = - notes_try_from_elements(&created_notes_data[created_note_ptr..]) - .map_err(TransactionResultError::OutputNoteDataInvalid)?; - created_notes.push(note_stub); - created_note_ptr += NOTE_MEM_SIZE as usize; - } - - let created_notes = - Self::new(created_notes).map_err(TransactionResultError::OutputNotesError)?; - if created_notes_commitment != created_notes.commitment() { - return Err(TransactionResultError::OutputNotesCommitmentInconsistent( - created_notes_commitment, - created_notes.commitment(), - )); - } - - Ok(created_notes) - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -fn extract_final_account_stub( - stack_outputs: &StackOutputs, - advice_map: &BTreeMap<[u8; 32], Vec>, -) -> Result { - let final_account_hash: Word = stack_outputs.stack() - [FINAL_ACCOUNT_HASH_WORD_IDX * WORD_SIZE..(FINAL_ACCOUNT_HASH_WORD_IDX + 1) * WORD_SIZE] - .iter() - .rev() - .map(|x| Felt::from(*x)) - .collect::>() - .try_into() - .expect("word size is correct"); - let final_account_hash: Digest = final_account_hash.into(); - - // extract final account data from the advice map - let final_account_data = group_slice_elements::( - advice_map - .get(&final_account_hash.as_bytes()) - .ok_or(TransactionResultError::FinalAccountDataNotFound)?, - ); - let stub = parse_final_account_stub(final_account_data) - .map_err(TransactionResultError::FinalAccountStubDataInvalid)?; - - Ok(stub) -} diff --git a/miden-tx/src/tests.rs b/miden-tx/src/tests.rs index 49a71ec87..15647192f 100644 --- a/miden-tx/src/tests.rs +++ b/miden-tx/src/tests.rs @@ -1,9 +1,10 @@ +use miden_lib::transaction::{ToTransactionKernelInputs, TransactionKernel}; use miden_objects::{ accounts::{Account, AccountCode}, assembly::{Assembler, ModuleAst, ProgramAst}, assets::{Asset, FungibleAsset}, block::BlockHeader, - transaction::{ChainMmr, InputNote, InputNotes, TransactionOutputs}, + transaction::{ChainMmr, InputNote, InputNotes}, Felt, Word, }; use miden_prover::ProvingOptions; @@ -22,7 +23,7 @@ use vm_processor::MemAdviceProvider; use super::{ AccountId, DataStore, DataStoreError, NoteOrigin, TransactionExecutor, TransactionHost, - TransactionInputs, TransactionProver, TransactionVerifier, TryFromVmResult, + TransactionInputs, TransactionProver, TransactionVerifier, }; // TESTS @@ -47,22 +48,17 @@ fn test_transaction_executor_witness() { let witness = transaction_result.clone().into_witness(); // use the witness to execute the transaction again - let mem_advice_provider: MemAdviceProvider = witness.advice_inputs().clone().into(); + let (stack_inputs, advice_inputs) = witness.get_kernel_inputs(); + let mem_advice_provider: MemAdviceProvider = advice_inputs.into(); let mut host = TransactionHost::new(mem_advice_provider); - let result = vm_processor::execute( - witness.program(), - witness.get_stack_inputs(), - &mut host, - Default::default(), - ) - .unwrap(); + let result = + vm_processor::execute(witness.program(), stack_inputs, &mut host, Default::default()) + .unwrap(); let (advice_provider, _event_handler) = host.into_parts(); - let (stack, map, store) = advice_provider.into_parts(); + let (_, map, _) = advice_provider.into_parts(); + let tx_outputs = TransactionKernel::parse_outputs(result.stack_outputs(), &map.into()).unwrap(); - let tx_outputs = - TransactionOutputs::try_from_vm_result(result.stack_outputs(), &stack, &map, &store) - .unwrap(); assert_eq!(transaction_result.final_account().hash(), tx_outputs.account.hash()); assert_eq!(transaction_result.output_notes(), &tx_outputs.output_notes); } diff --git a/objects/src/errors.rs b/objects/src/errors.rs index e5c46769b..887778b1f 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -283,6 +283,7 @@ pub enum TransactionError { output_id: AccountId, }, InvalidAccountSeed(AccountError), + InvalidInputNoteDataLength, ScriptCompilationError(AssemblyError), TooManyInputNotes { max: usize, diff --git a/objects/src/lib.rs b/objects/src/lib.rs index 267843a9c..fc4452265 100644 --- a/objects/src/lib.rs +++ b/objects/src/lib.rs @@ -37,7 +37,7 @@ pub mod crypto { pub mod utils { pub use miden_crypto::utils::{format, vec}; - pub use vm_core::utils::{collections, string, IntoBytes}; + pub use vm_core::utils::{collections, group_slice_elements, string, IntoBytes}; pub mod serde { pub use miden_crypto::utils::{ @@ -48,5 +48,5 @@ pub mod utils { pub mod vm { pub use vm_core::{code_blocks::CodeBlock, Program}; - pub use vm_processor::{AdviceInputs, StackInputs}; + pub use vm_processor::{AdviceInputs, StackInputs, StackOutputs}; } diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index 6f6da50c9..df583e478 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -168,6 +168,15 @@ impl PartialEq for InputNotes { impl Eq for InputNotes {} +impl Default for InputNotes { + fn default() -> Self { + Self { + notes: Vec::new(), + commitment: OnceCell::new(), + } + } +} + // SERIALIZATION // ------------------------------------------------------------------------------------------------ diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index c99ef867c..2d6e0500b 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -1,9 +1,8 @@ use super::{ accounts::{Account, AccountId}, notes::{NoteEnvelope, Nullifier}, - utils::collections::Vec, - vm::{AdviceInputs, Program, StackInputs}, - BlockHeader, Digest, Felt, Hasher, StarkField, TransactionWitnessError, Word, WORD_SIZE, ZERO, + vm::{AdviceInputs, Program}, + BlockHeader, Digest, Felt, Hasher, Word, WORD_SIZE, ZERO, }; mod chain_mmr; diff --git a/objects/src/transaction/tx_witness.rs b/objects/src/transaction/tx_witness.rs index 622c9cdc3..51e509604 100644 --- a/objects/src/transaction/tx_witness.rs +++ b/objects/src/transaction/tx_witness.rs @@ -1,7 +1,4 @@ -use super::{ - AccountId, AdviceInputs, Digest, Felt, Hasher, Nullifier, Program, StackInputs, StarkField, - TransactionWitnessError, Vec, Word, WORD_SIZE, -}; +use super::{AccountId, AdviceInputs, Digest, Felt, Program}; // TRANSACTION WITNESS // ================================================================================================ @@ -70,62 +67,8 @@ impl TransactionWitness { } /// Returns the block hash of the latest known block. - pub fn block_hash(&self) -> &Digest { - &self.block_hash - } - - /// Returns a vector of [Nullifier] for all consumed notes in the transaction. - /// - /// # Errors - /// - If the consumed notes data is not found in the advice map. - /// - If the consumed notes data is not well formed. - 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.input_notes_hash.as_bytes()) - .ok_or(TransactionWitnessError::ConsumedNoteDataNotFound)?; - - // extract the notes from the first fetch and instantiate a vector to hold nullifiers - let num_notes = notes_data[0].as_int(); - 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; - while note_ptr < notes_data.len() { - // make sure there is enough data to read (note data is well formed) - if note_ptr + 5 * WORD_SIZE > notes_data.len() { - return Err(TransactionWitnessError::InvalidConsumedNoteDataLength); - } - - // compute the nullifier and extract script root and number of assets - let (nullifier, num_assets) = extract_note_data(¬es_data[note_ptr..]); - - // push the [ConsumedNoteInfo] to the vector - 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; - - // increment note pointer - note_ptr += (num_assets as usize * WORD_SIZE) + 30; - } - - debug_assert_eq!( - self.input_notes_hash, - Hasher::hash_elements( - &input_notes_info - .iter() - .flat_map(|info| { - let mut elements = Word::from(info).to_vec(); - elements.extend_from_slice(&Word::default()); - elements - }) - .collect::>() - ) - ); - - Ok(input_notes_info) + pub fn block_hash(&self) -> Digest { + self.block_hash } /// Returns the transaction script root. @@ -138,57 +81,17 @@ impl TransactionWitness { &self.program } - /// 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.input_notes_hash); - inputs.extend(*self.initial_account_hash); - inputs.push(self.account_id.into()); - inputs.extend(*self.block_hash); - StackInputs::new(inputs) - } - /// Returns the advice inputs for the transaction. pub fn advice_inputs(&self) -> &AdviceInputs { &self.advice_witness } - // CONSUMERS + // ADVICE DATA EXTRACTORS // -------------------------------------------------------------------------------------------- - /// Consumes the witness and returns its parts. - pub fn into_parts( - self, - ) -> (AccountId, Digest, Digest, Digest, Option, Program, AdviceInputs) { - ( - self.account_id, - self.initial_account_hash, - self.block_hash, - self.input_notes_hash, - self.tx_script_root, - self.program, - self.advice_witness, - ) - } -} - -// HELPERS -// ================================================================================================ -/// Extracts and returns the nullifier and the number of assets from the provided note data. -/// -/// Expects the note data to be organized as follows: -/// [CN_SN, CN_SR, CN_IR, CN_VR, CN_M] -/// -/// - CN_SN is the serial number of the consumed note. -/// - CN_SR is the script root of the consumed note. -/// - CN_IR is the inputs root of the consumed note. -/// - CN_VR is the vault root of the consumed note. -/// - CN1_M is the metadata of the consumed note. -fn extract_note_data(note_data: &[Felt]) -> (Digest, u64) { - // compute the nullifier - let nullifier = Hasher::hash_elements(¬e_data[..4 * WORD_SIZE]); - // extract the number of assets - let num_assets = note_data[4 * WORD_SIZE].as_int(); - - (nullifier, num_assets) + /// Returns data from the advice map located under `self.input_notes_hash` key. + pub fn input_note_data(&self) -> Option<&[Felt]> { + // TODO: return None if input_notes_hash == EMPTY_WORD? + self.advice_witness.mapped_values(&self.input_notes_hash.as_bytes()) + } } From f952ea7ba826316504c9847efc51800e7ba80cf5 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Sat, 30 Dec 2023 23:02:29 -0800 Subject: [PATCH 16/21] refactor: move output stack construction logic out of transaction verifier --- miden-lib/src/transaction/mod.rs | 34 +++++++++++-- miden-tx/src/compiler/mod.rs | 6 --- miden-tx/src/verifier/mod.rs | 87 +++++++++++--------------------- objects/src/lib.rs | 2 +- 4 files changed, 62 insertions(+), 67 deletions(-) diff --git a/miden-lib/src/transaction/mod.rs b/miden-lib/src/transaction/mod.rs index 16ed984f4..2a43aedf3 100644 --- a/miden-lib/src/transaction/mod.rs +++ b/miden-lib/src/transaction/mod.rs @@ -1,4 +1,4 @@ -use assembly::{ast::ProgramAst, utils::DeserializationError, Assembler}; +use assembly::{ast::ProgramAst, utils::DeserializationError, Assembler, AssemblyContext}; use miden_objects::{ accounts::AccountId, notes::Nullifier, @@ -7,7 +7,7 @@ use miden_objects::{ collections::{BTreeMap, Vec}, group_slice_elements, }, - vm::{StackInputs, StackOutputs}, + vm::{ProgramInfo, StackInputs, StackOutputs}, Digest, Felt, Hasher, StarkField, TransactionError, TransactionResultError, Word, WORD_SIZE, }; use miden_stdlib::StdLibrary; @@ -48,6 +48,21 @@ impl TransactionKernel { ProgramAst::from_bytes(kernel_bytes) } + /// Returns [ProgramInfo] for the main executable of the transaction kernel. + /// + /// # Panics + /// Panics if the transaction kernel source is not well-formed. + pub fn program_info() -> ProgramInfo { + // TODO: construct kernel_main and kernel using lazy static or at build time + let assembler = Self::assembler(); + let main_ast = TransactionKernel::main().expect("main is well formed"); + let kernel_main = assembler + .compile_in_context(&main_ast, &mut AssemblyContext::for_program(Some(&main_ast))) + .expect("main is well formed"); + + ProgramInfo::new(kernel_main.hash(), assembler.kernel().clone()) + } + // ASSEMBLER CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -63,7 +78,7 @@ impl TransactionKernel { .expect("kernel is well formed") } - // INPUT / OUTPUT STACK BUILDERS + // STACK INPUTS / OUTPUTS // -------------------------------------------------------------------------------------------- /// Returns the input stack required to execute the transaction kernel. @@ -94,6 +109,19 @@ impl TransactionKernel { StackInputs::new(inputs) } + pub fn build_output_stack( + final_acct_hash: Digest, + output_notes_hash: Digest, + tx_script_root: Option, + ) -> StackOutputs { + let mut outputs: Vec = Vec::with_capacity(9); + outputs.extend(final_acct_hash); + outputs.extend(output_notes_hash); + outputs.extend(tx_script_root.unwrap_or_default()); + outputs.reverse(); + StackOutputs::from_elements(outputs, Vec::new()).unwrap() + } + /// TODO: finish description /// /// Stack: [TXSR, CNC, FAH] diff --git a/miden-tx/src/compiler/mod.rs b/miden-tx/src/compiler/mod.rs index 332098850..41a59da1d 100644 --- a/miden-tx/src/compiler/mod.rs +++ b/miden-tx/src/compiler/mod.rs @@ -3,7 +3,6 @@ use miden_objects::{ transaction::{InputNotes, TransactionScript}, Felt, TransactionError, Word, }; -use vm_processor::ProgramInfo; use super::{ AccountCode, AccountId, BTreeMap, CodeBlock, Digest, NoteScript, Program, @@ -198,11 +197,6 @@ impl TransactionCompiler { Ok(program) } - /// Returns a [ProgramInfo] associated with the transaction kernel program. - pub fn build_program_info(&self) -> ProgramInfo { - ProgramInfo::new(self.kernel_main.hash(), self.assembler.kernel().clone()) - } - // HELPER METHODS // -------------------------------------------------------------------------------------------- diff --git a/miden-tx/src/verifier/mod.rs b/miden-tx/src/verifier/mod.rs index 8839ab1f6..0c8b7dd77 100644 --- a/miden-tx/src/verifier/mod.rs +++ b/miden-tx/src/verifier/mod.rs @@ -1,15 +1,13 @@ -use core::ops::Range; - -use miden_lib::transaction::{ - FINAL_ACCOUNT_HASH_WORD_IDX, OUTPUT_NOTES_COMMITMENT_WORD_IDX, TX_SCRIPT_ROOT_WORD_IDX, -}; -use miden_objects::{transaction::ProvenTransaction, Felt, WORD_SIZE, ZERO}; +use miden_lib::transaction::TransactionKernel; +use miden_objects::{transaction::ProvenTransaction, vm::ProgramInfo}; use miden_verifier::verify; -use vm_core::{stack::STACK_TOP_SIZE, ProgramInfo, StackInputs, StackOutputs}; -use super::{TransactionCompiler, TransactionVerifierError}; +use super::TransactionVerifierError; + +// TRANSACTION VERIFIER +// ================================================================================================ -/// The [TransactionVerifier] is used to verify a [ProvenTransaction]. +/// The [TransactionVerifier] is used to verify [ProvenTransaction]s. /// /// The [TransactionVerifier] contains a [ProgramInfo] object which is associated with the /// transaction kernel program. The `proof_security_level` specifies the minimum security @@ -20,27 +18,42 @@ pub struct TransactionVerifier { } impl TransactionVerifier { - /// Creates a new [TransactionVerifier] object. + /// Returns a new [TransactionVerifier] instantiated with the specified security level. pub fn new(proof_security_level: u32) -> Self { - // TODO: create program info at build time? - let tx_program_info = TransactionCompiler::new().build_program_info(); + let tx_program_info = TransactionKernel::program_info(); Self { tx_program_info, proof_security_level } } - /// Verifies the provided [ProvenTransaction] against the kernel. + /// Verifies the provided [ProvenTransaction] against the transaction kernel. /// /// # Errors - /// - if transaction verification fails. - /// - if the proof security level is insufficient. + /// Returns an error if: + /// - Transaction verification fails. + /// - The security level of the verified proof is insufficient. pub fn verify(&self, transaction: ProvenTransaction) -> Result<(), TransactionVerifierError> { + // build stack inputs and outputs + let stack_inputs = TransactionKernel::build_input_stack( + transaction.account_id(), + Some(transaction.initial_account_hash()), + transaction.input_notes().commitment(), + transaction.block_ref(), + ); + let stack_outputs = TransactionKernel::build_output_stack( + transaction.final_account_hash(), + transaction.output_notes().commitment(), + transaction.tx_script_root(), + ); + + // verify transaction proof let proof_security_level = verify( self.tx_program_info.clone(), - Self::build_stack_inputs(&transaction), - Self::build_stack_outputs(&transaction), + stack_inputs, + stack_outputs, transaction.proof().clone(), ) .map_err(TransactionVerifierError::TransactionVerificationFailed)?; + // check security level if proof_security_level < self.proof_security_level { return Err(TransactionVerifierError::InsufficientProofSecurityLevel( proof_security_level, @@ -50,44 +63,4 @@ impl TransactionVerifier { Ok(()) } - - // HELPERS - // -------------------------------------------------------------------------------------------- - - /// Returns the stack inputs for the transaction. - fn build_stack_inputs(transaction: &ProvenTransaction) -> StackInputs { - let mut stack_inputs: Vec = Vec::with_capacity(13); - stack_inputs.extend_from_slice(transaction.input_notes().commitment().as_elements()); - stack_inputs.extend_from_slice(transaction.initial_account_hash().as_elements()); - stack_inputs.push(transaction.account_id().into()); - stack_inputs.extend_from_slice(transaction.block_ref().as_elements()); - StackInputs::new(stack_inputs) - } - - /// Returns the stack outputs for the transaction. - fn build_stack_outputs(transaction: &ProvenTransaction) -> StackOutputs { - const TX_SCRIPT_ROOT_RANGE: Range = Range { - start: STACK_TOP_SIZE - ((TX_SCRIPT_ROOT_WORD_IDX + 1) * WORD_SIZE), - end: STACK_TOP_SIZE - (TX_SCRIPT_ROOT_WORD_IDX * WORD_SIZE), - }; - const CREATED_NOTES_COMMITMENT_RANGE: Range = Range { - start: STACK_TOP_SIZE - ((OUTPUT_NOTES_COMMITMENT_WORD_IDX + 1) * WORD_SIZE), - end: STACK_TOP_SIZE - (OUTPUT_NOTES_COMMITMENT_WORD_IDX * WORD_SIZE), - }; - const FINAL_ACCOUNT_HASH_RANGE: Range = Range { - start: STACK_TOP_SIZE - ((FINAL_ACCOUNT_HASH_WORD_IDX + 1) * WORD_SIZE), - end: STACK_TOP_SIZE - (FINAL_ACCOUNT_HASH_WORD_IDX * WORD_SIZE), - }; - - let mut stack_outputs: Vec = vec![ZERO; STACK_TOP_SIZE]; - stack_outputs[TX_SCRIPT_ROOT_RANGE] - .copy_from_slice(transaction.tx_script_root().unwrap_or_default().as_elements()); - stack_outputs[CREATED_NOTES_COMMITMENT_RANGE] - .copy_from_slice(transaction.output_notes().commitment().as_elements()); - stack_outputs[FINAL_ACCOUNT_HASH_RANGE] - .copy_from_slice(transaction.final_account_hash().as_elements()); - stack_outputs.reverse(); - StackOutputs::from_elements(stack_outputs, Default::default()) - .expect("StackOutputs are valid") - } } diff --git a/objects/src/lib.rs b/objects/src/lib.rs index fc4452265..04989b545 100644 --- a/objects/src/lib.rs +++ b/objects/src/lib.rs @@ -47,6 +47,6 @@ pub mod utils { } pub mod vm { - pub use vm_core::{code_blocks::CodeBlock, Program}; + pub use vm_core::{code_blocks::CodeBlock, Program, ProgramInfo}; pub use vm_processor::{AdviceInputs, StackInputs, StackOutputs}; } From 79f72d2aa4c5fd0f6e5f5b1a0d3881d59616d0e9 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Sat, 30 Dec 2023 23:42:14 -0800 Subject: [PATCH 17/21] refactor: remove proving of prepared transactions --- miden-lib/src/transaction/mod.rs | 2 +- miden-tx/src/compiler/mod.rs | 8 +++-- miden-tx/src/executor/mod.rs | 17 ++++----- miden-tx/src/prover/mod.rs | 49 ++++---------------------- miden-tx/src/tests.rs | 45 +++++------------------ objects/src/transaction/executed_tx.rs | 24 ++++++------- 6 files changed, 42 insertions(+), 103 deletions(-) diff --git a/miden-lib/src/transaction/mod.rs b/miden-lib/src/transaction/mod.rs index 2a43aedf3..14850257f 100644 --- a/miden-lib/src/transaction/mod.rs +++ b/miden-lib/src/transaction/mod.rs @@ -48,7 +48,7 @@ impl TransactionKernel { ProgramAst::from_bytes(kernel_bytes) } - /// Returns [ProgramInfo] for the main executable of the transaction kernel. + /// Returns [ProgramInfo] for the transaction kernel executable program. /// /// # Panics /// Panics if the transaction kernel source is not well-formed. diff --git a/miden-tx/src/compiler/mod.rs b/miden-tx/src/compiler/mod.rs index 41a59da1d..0ec3bf5b2 100644 --- a/miden-tx/src/compiler/mod.rs +++ b/miden-tx/src/compiler/mod.rs @@ -32,7 +32,7 @@ pub struct TransactionCompiler { impl TransactionCompiler { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- - /// Returns a new instance of the [TransactionComplier]. + /// Returns a new [TransactionCompiler]. pub fn new() -> TransactionCompiler { let assembler = TransactionKernel::assembler(); @@ -357,8 +357,10 @@ fn recursively_collect_call_branches(code_block: &CodeBlock, branches: &mut Vec< // ================================================================================================ /// The [ScriptTarget] enum is used to specify the target account interface for note and -/// transaction scripts. This is specified as an account ID (for which the interface should be -/// fetched) or a vector of procedure digests which represents the account interface. +/// transaction scripts. +/// +/// This is specified as an account ID (for which the interface should be fetched) or a vector of +/// procedure digests which represents the account interface. #[derive(Clone)] pub enum ScriptTarget { AccountId(AccountId), diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index 57cf61c6e..8a55ce1a4 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -26,12 +26,12 @@ use crate::host::EventHandler; /// - Compile the transaction into a program using the [TransactionComplier](crate::TransactionCompiler). /// - Execute the transaction program and create an [ExecutedTransaction]. /// -/// The [TransactionExecutor] is generic over the [DataStore] which allows it to be used with +/// The transaction executor is generic over the [DataStore] which allows it to be used with /// different data backend implementations. /// /// The [TransactionExecutor::execute_transaction()] method is the main entry point for the -/// executor and produces a [ExecutedTransaction] for the transaction. The executed transaction can -/// then be used to by the prover to generate a proof transaction execution. +/// executor and produces an [ExecutedTransaction] for the transaction. The executed transaction +/// can then be used to by the prover to generate a proof transaction execution. pub struct TransactionExecutor { compiler: TransactionCompiler, data_store: D, @@ -75,9 +75,9 @@ impl TransactionExecutor { .map_err(TransactionExecutorError::LoadAccountFailed) } - /// Loads the provided account interface (vector of procedure digests) into the the compiler. + /// Loads the provided account interface (vector of procedure digests) into the compiler. /// - /// Returns the old account interface if it previously existed. + /// Returns the old interface for the specified account ID if it previously existed. pub fn load_account_interface( &mut self, account_id: AccountId, @@ -86,8 +86,9 @@ impl TransactionExecutor { self.compiler.load_account_interface(account_id, procedures) } - /// Compiles the provided program into the [NoteScript] and checks (to the extent possible) - /// if a note could be executed against all accounts with the specified interfaces. + /// Compiles the provided program into a [NoteScript] and checks (to the extent possible) if + /// the specified note program could be executed against all accounts with the specified + /// interfaces. pub fn compile_note_script( &mut self, note_script_ast: ProgramAst, @@ -176,7 +177,7 @@ impl TransactionExecutor { /// Returns an error if: /// - If required data can not be fetched from the [DataStore]. /// - If the transaction can not be compiled. - pub(crate) fn prepare_transaction( + fn prepare_transaction( &mut self, account_id: AccountId, block_ref: u32, diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 543d6df22..a1fcfa568 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -1,7 +1,5 @@ use miden_lib::transaction::{ToTransactionKernelInputs, TransactionKernel}; -use miden_objects::transaction::{ - InputNotes, PreparedTransaction, ProvenTransaction, TransactionWitness, -}; +use miden_objects::transaction::{InputNotes, ProvenTransaction, TransactionWitness}; use miden_prover::prove; pub use miden_prover::ProvingOptions; use vm_processor::MemAdviceProvider; @@ -24,43 +22,8 @@ impl TransactionProver { Self { proof_options } } - /// Proves the provided [PreparedTransaction] and returns a [ProvenTransaction]. - /// - /// # Errors - /// - If the transaction program cannot be proven. - /// - If the transaction result is corrupt. - pub fn prove_prepared_transaction( - &self, - transaction: PreparedTransaction, - ) -> Result { - let (stack_inputs, advice_inputs) = transaction.get_kernel_inputs(); - let advice_provider: MemAdviceProvider = advice_inputs.into(); - let mut host = TransactionHost::new(advice_provider); - - let (stack_outputs, proof) = - prove(transaction.program(), stack_inputs, &mut host, self.proof_options.clone()) - .map_err(TransactionProverError::ProveTransactionProgramFailed)?; - - // extract transaction outputs and process transaction data - let (advice_provider, _event_handler) = host.into_parts(); - let (_, map, _) = advice_provider.into_parts(); - let adv_map = map.into(); - let tx_outputs = TransactionKernel::parse_outputs(&stack_outputs, &adv_map) - .map_err(TransactionProverError::TransactionResultError)?; - - let (_tx_program, tx_script, tx_inputs) = transaction.into_parts(); - - Ok(ProvenTransaction::new( - tx_inputs.account.id(), - tx_inputs.account.hash(), - tx_outputs.account.hash(), - tx_inputs.input_notes.into(), - tx_outputs.output_notes.into(), - tx_script.map(|tx_script| *tx_script.hash()), - tx_inputs.block_header.hash(), - proof, - )) - } + // TRANSACTION PROVER + // -------------------------------------------------------------------------------------------- /// Proves the provided [TransactionWitness] and returns a [ProvenTransaction]. /// @@ -68,10 +31,12 @@ impl TransactionProver { /// - If the consumed note data in the transaction witness is corrupt. /// - If the transaction program cannot be proven. /// - If the transaction result is corrupt. - pub fn prove_transaction_witness( + pub fn prove_transaction>( &self, - tx_witness: TransactionWitness, + transaction: T, ) -> Result { + let tx_witness: TransactionWitness = transaction.into(); + // extract required data from the transaction witness let (stack_inputs, advice_inputs) = tx_witness.get_kernel_inputs(); diff --git a/miden-tx/src/tests.rs b/miden-tx/src/tests.rs index 15647192f..2693185a9 100644 --- a/miden-tx/src/tests.rs +++ b/miden-tx/src/tests.rs @@ -4,7 +4,7 @@ use miden_objects::{ assembly::{Assembler, ModuleAst, ProgramAst}, assets::{Asset, FungibleAsset}, block::BlockHeader, - transaction::{ChainMmr, InputNote, InputNotes}, + transaction::{ChainMmr, InputNote, InputNotes, TransactionWitness}, Felt, Word, }; use miden_prover::ProvingOptions; @@ -42,25 +42,25 @@ fn test_transaction_executor_witness() { data_store.notes.iter().map(|note| note.origin().clone()).collect::>(); // execute the transaction and get the witness - let transaction_result = executor + let executed_transaction = executor .execute_transaction(account_id, block_ref, ¬e_origins, None) .unwrap(); - let witness = transaction_result.clone().into_witness(); + let tx_witness: TransactionWitness = executed_transaction.clone().into(); // use the witness to execute the transaction again - let (stack_inputs, advice_inputs) = witness.get_kernel_inputs(); + let (stack_inputs, advice_inputs) = tx_witness.get_kernel_inputs(); let mem_advice_provider: MemAdviceProvider = advice_inputs.into(); let mut host = TransactionHost::new(mem_advice_provider); let result = - vm_processor::execute(witness.program(), stack_inputs, &mut host, Default::default()) + vm_processor::execute(tx_witness.program(), stack_inputs, &mut host, Default::default()) .unwrap(); let (advice_provider, _event_handler) = host.into_parts(); let (_, map, _) = advice_provider.into_parts(); let tx_outputs = TransactionKernel::parse_outputs(result.stack_outputs(), &map.into()).unwrap(); - assert_eq!(transaction_result.final_account().hash(), tx_outputs.account.hash()); - assert_eq!(transaction_result.output_notes(), &tx_outputs.output_notes); + assert_eq!(executed_transaction.final_account().hash(), tx_outputs.account.hash()); + assert_eq!(executed_transaction.output_notes(), &tx_outputs.output_notes); } #[test] @@ -274,41 +274,14 @@ fn test_prove_witness_and_verify() { data_store.notes.iter().map(|note| note.origin().clone()).collect::>(); // execute the transaction and get the witness - let transaction_result = executor + let executed_transaction = executor .execute_transaction(account_id, block_ref, ¬e_origins, None) .unwrap(); - let witness = transaction_result.clone().into_witness(); // prove the transaction with the witness let proof_options = ProvingOptions::default(); let prover = TransactionProver::new(proof_options); - let proven_transaction = prover.prove_transaction_witness(witness).unwrap(); - - let verifier = TransactionVerifier::new(96); - assert!(verifier.verify(proven_transaction).is_ok()); -} - -#[test] -fn test_prove_and_verify_with_tx_executor() { - let data_store = MockDataStore::default(); - let mut executor = TransactionExecutor::new(data_store.clone()); - - let account_id = data_store.account.id(); - executor.load_account(account_id).unwrap(); - - let block_ref = data_store.block_header.block_num(); - let note_origins = - data_store.notes.iter().map(|note| note.origin().clone()).collect::>(); - - // prove the transaction with the executor - let prepared_transaction = executor - .prepare_transaction(account_id, block_ref, ¬e_origins, None) - .unwrap(); - - // prove transaction - let proof_options = ProvingOptions::default(); - let prover = TransactionProver::new(proof_options); - let proven_transaction = prover.prove_prepared_transaction(prepared_transaction).unwrap(); + let proven_transaction = prover.prove_transaction(executed_transaction).unwrap(); let verifier = TransactionVerifier::new(96); assert!(verifier.verify(proven_transaction).is_ok()); diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index 259b2db19..3045c8f05 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -131,20 +131,18 @@ impl ExecutedTransaction { pub fn tx_inputs(&self) -> &TransactionInputs { &self.tx_inputs } +} - // CONVERSIONS - // -------------------------------------------------------------------------------------------- - - /// Converts this transaction into a [TransactionWitness]. - pub fn into_witness(self) -> TransactionWitness { - TransactionWitness::new( - self.initial_account().id(), - self.initial_account().hash(), - self.block_header().hash(), - self.input_notes().commitment(), - self.tx_script().map(|s| *s.hash()), - self.program, - self.advice_witness, +impl From for TransactionWitness { + fn from(tx: ExecutedTransaction) -> Self { + Self::new( + tx.initial_account().id(), + tx.initial_account().hash(), + tx.block_header().hash(), + tx.input_notes().commitment(), + tx.tx_script().map(|s| *s.hash()), + tx.program, + tx.advice_witness, ) } } From 6b911a356bcb7ca9fc5657ed1f75332a38a1517a Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Sun, 31 Dec 2023 02:22:36 -0800 Subject: [PATCH 18/21] refactor: move full transaction inputs into TransactionWitness --- miden-lib/src/tests/test_prologue.rs | 4 +- miden-lib/src/transaction/inputs.rs | 64 +++++++----- miden-lib/src/transaction/mod.rs | 130 +++++-------------------- miden-tx/src/executor/mod.rs | 2 +- miden-tx/src/prover/mod.rs | 32 +++--- miden-tx/src/tests.rs | 3 +- objects/src/transaction/executed_tx.rs | 44 ++++++--- objects/src/transaction/inputs.rs | 9 ++ objects/src/transaction/mod.rs | 2 +- objects/src/transaction/prepared_tx.rs | 15 +-- objects/src/transaction/tx_witness.rs | 101 +++++++++---------- 11 files changed, 173 insertions(+), 233 deletions(-) diff --git a/miden-lib/src/tests/test_prologue.rs b/miden-lib/src/tests/test_prologue.rs index 0cadc8435..23cada695 100644 --- a/miden-lib/src/tests/test_prologue.rs +++ b/miden-lib/src/tests/test_prologue.rs @@ -190,10 +190,10 @@ fn chain_mmr_memory_assertions( // The number of leaves should be stored at the CHAIN_MMR_NUM_LEAVES_PTR assert_eq!( process.get_mem_value(ContextId::root(), CHAIN_MMR_NUM_LEAVES_PTR).unwrap()[0], - Felt::new(inputs.block_chain().chain_length() as u64) + Felt::new(inputs.tx_inputs().block_chain.chain_length() as u64) ); - for (i, peak) in inputs.block_chain().peaks().peaks().iter().enumerate() { + for (i, peak) in inputs.tx_inputs().block_chain.peaks().peaks().iter().enumerate() { // The peaks should be stored at the CHAIN_MMR_PEAKS_PTR let i: u32 = i.try_into().expect( "Number of peaks is log2(number_of_leaves), this value won't be larger than 2**32", diff --git a/miden-lib/src/transaction/inputs.rs b/miden-lib/src/transaction/inputs.rs index 29478c24a..56ab58a71 100644 --- a/miden-lib/src/transaction/inputs.rs +++ b/miden-lib/src/transaction/inputs.rs @@ -29,7 +29,10 @@ impl ToTransactionKernelInputs for PreparedTransaction { self.input_notes().commitment(), self.block_header().hash(), ); - let advice_inputs = build_advice_inputs(self.tx_inputs(), self.tx_script()); + + let mut advice_inputs = AdviceInputs::default(); + extend_advice_inputs(self.tx_inputs(), self.tx_script(), &mut advice_inputs); + (stack_inputs, advice_inputs) } } @@ -43,47 +46,53 @@ impl ToTransactionKernelInputs for ExecutedTransaction { self.input_notes().commitment(), self.block_header().hash(), ); - let advice_inputs = build_advice_inputs(self.tx_inputs(), self.tx_script()); + + let mut advice_inputs = self.advice_witness().clone(); + extend_advice_inputs(self.tx_inputs(), self.tx_script(), &mut advice_inputs); + (stack_inputs, advice_inputs) } } impl ToTransactionKernelInputs for TransactionWitness { fn get_kernel_inputs(&self) -> (StackInputs, AdviceInputs) { + let account = self.account(); let stack_inputs = TransactionKernel::build_input_stack( - self.account_id(), - Some(self.initial_account_hash()), // TODO - self.input_notes_hash(), - self.block_hash(), + account.id(), + if account.is_new() { None } else { Some(account.hash()) }, + self.input_notes().commitment(), + self.block_header().hash(), ); - (stack_inputs, self.advice_inputs().clone()) + let mut advice_inputs = self.advice_witness().clone(); + extend_advice_inputs(self.tx_inputs(), self.tx_script(), &mut advice_inputs); + + (stack_inputs, advice_inputs) } } // ADVICE INPUTS // ================================================================================================ -/// Returns the advice inputs required for executing a transaction with the specified inputs. +/// Extends the provided advice inputs with the data required for executing a transaction with the +/// specified inputs. /// -/// This includes the initial account, an optional account seed (required for new accounts), the -/// number of consumed notes, the core consumed note data, and the consumed note inputs. -fn build_advice_inputs( +/// This includes the initial account, an optional account seed (required for new accounts), and +/// the input note data, including core note data + authentication paths all the way to the root +/// of one of chain MMR peaks. +fn extend_advice_inputs( tx_inputs: &TransactionInputs, tx_script: Option<&TransactionScript>, -) -> AdviceInputs { - let mut advice_inputs = AdviceInputs::default(); - + advice_inputs: &mut AdviceInputs, +) { // build the advice stack - build_advice_stack(tx_inputs, tx_script, &mut advice_inputs); + build_advice_stack(tx_inputs, tx_script, advice_inputs); // build the advice map and Merkle store for relevant components - add_chain_mmr_to_advice_inputs(&tx_inputs.block_chain, &mut advice_inputs); - add_account_to_advice_inputs(&tx_inputs.account, tx_inputs.account_seed, &mut advice_inputs); - add_input_notes_to_advice_inputs(&tx_inputs.input_notes, &mut advice_inputs); - add_tx_script_inputs_to_advice_map(tx_script, &mut advice_inputs); - - advice_inputs + add_chain_mmr_to_advice_inputs(&tx_inputs.block_chain, advice_inputs); + add_account_to_advice_inputs(&tx_inputs.account, tx_inputs.account_seed, advice_inputs); + add_input_notes_to_advice_inputs(&tx_inputs.input_notes, advice_inputs); + add_tx_script_inputs_to_advice_map(tx_script, advice_inputs); } // ADVICE STACK BUILDER @@ -291,7 +300,18 @@ fn add_input_notes_to_advice_inputs(notes: &InputNotes, inputs: &mut AdviceInput ); // add the note elements to the combined vector of note data - TransactionKernel::write_input_note_into(input_note, &mut note_data); + note_data.extend(note.serial_num()); + note_data.extend(*note.script().hash()); + note_data.extend(*note.inputs().hash()); + note_data.extend(*note.vault().hash()); + note_data.extend(Word::from(note.metadata())); + + note_data.extend(note.vault().to_padded_assets()); + + note_data.push(proof.origin().block_num.into()); + note_data.extend(*proof.sub_hash()); + note_data.extend(*proof.note_root()); + note_data.push(proof.origin().node_index.value().into()); } // insert the combined note data into the advice map diff --git a/miden-lib/src/transaction/mod.rs b/miden-lib/src/transaction/mod.rs index 14850257f..427f14eae 100644 --- a/miden-lib/src/transaction/mod.rs +++ b/miden-lib/src/transaction/mod.rs @@ -1,14 +1,13 @@ use assembly::{ast::ProgramAst, utils::DeserializationError, Assembler, AssemblyContext}; use miden_objects::{ accounts::AccountId, - notes::Nullifier, - transaction::{InputNote, OutputNotes, TransactionOutputs}, + transaction::{OutputNotes, TransactionOutputs}, utils::{ collections::{BTreeMap, Vec}, group_slice_elements, }, vm::{ProgramInfo, StackInputs, StackOutputs}, - Digest, Felt, Hasher, StarkField, TransactionError, TransactionResultError, Word, WORD_SIZE, + Digest, Felt, TransactionResultError, Word, }; use miden_stdlib::StdLibrary; @@ -122,7 +121,9 @@ impl TransactionKernel { StackOutputs::from_elements(outputs, Vec::new()).unwrap() } - /// TODO: finish description + /// Extracts transaction output data from the provided stack outputs. + /// + /// The data on the stack is expected to be arranged as follows: /// /// Stack: [TXSR, CNC, FAH] /// @@ -133,18 +134,31 @@ impl TransactionKernel { /// executed against. pub fn parse_output_stack(stack: &StackOutputs) -> (Digest, Digest, Digest) { // TODO: use constants - let tx_script_root = stack.get_stack_word(0).expect("msg").into(); - let output_notes_hash = stack.get_stack_word(4).expect("msg").into(); - let final_account_hash = stack.get_stack_word(8).expect("msg").into(); + let tx_script_root = stack.get_stack_word(0).expect("first word missing").into(); + let output_notes_hash = stack.get_stack_word(4).expect("second word missing").into(); + let final_account_hash = stack.get_stack_word(8).expect("third word missing").into(); (final_account_hash, output_notes_hash, tx_script_root) } - // ADVICE MAP EXTRACTORS + // TRANSACTION OUTPUT PARSER // -------------------------------------------------------------------------------------------- - /// TODO: add comments - pub fn parse_outputs( + /// Returns [TransactionOutputs] constructed from the provided output stack and advice map. + /// + /// The output stack is expected to be arrange as follows: + /// + /// Stack: [TXSR, CNC, FAH] + /// + /// Where: + /// - TXSR is the transaction script root. + /// - CNC is the commitment to the notes created by the transaction. + /// - FAH is the final account hash of the account that the transaction is being + /// executed against. + /// + /// The actual data describing the new account state and output notes is expected to be located + /// in the provided advice map under keys CNC and FAH. + pub fn parse_transaction_outputs( stack: &StackOutputs, adv_map: &AdviceMap, ) -> Result { @@ -187,102 +201,6 @@ impl TransactionKernel { Ok(TransactionOutputs { account, output_notes }) } - - // NOTE DATA BUILDER - // -------------------------------------------------------------------------------------------- - - /// TODO - pub fn write_input_note_into(input_note: &InputNote, target: &mut Vec) { - let note = input_note.note(); - let proof = input_note.proof(); - - // write the note info; 20 elements - target.extend(note.serial_num()); - target.extend(*note.script().hash()); - target.extend(*note.inputs().hash()); - target.extend(*note.vault().hash()); - target.extend(Word::from(note.metadata())); - - // write asset vault; 4 * num_assets elements - target.extend(note.vault().to_padded_assets()); - - // write note location info; 10 elements - target.push(proof.origin().block_num.into()); - target.extend(*proof.sub_hash()); - target.extend(*proof.note_root()); - target.push(proof.origin().node_index.value().into()); - } - - /// Returns a vectors of nullifiers read from the provided note data stream. - /// - /// Notes are expected to be arranged in the stream as follows: - /// - /// [n, note_1_data, ... note_n_data] - /// - /// where n is the number of notes in the stream. Each note is expected to be arranged as - /// follows: - /// - /// [serial_num, script_hash, input_hash, vault_hash, metadata, asset_1 ... asset_k, - /// block_num, sub_hash, notes_root, note_index] - /// - /// Thus, the number of elements - /// - /// # Errors - /// Returns an error if: - /// - The stream does not contain at least one note. - /// - The stream does not have enough data to read the specified number of notes. - /// - The stream is not fully consumed after all notes have been processed. - pub fn read_input_nullifiers_from(source: &[Felt]) -> Result, TransactionError> { - // extract the notes from the first fetch and instantiate a vector to hold nullifiers - let num_notes = source[0].as_int(); - let mut nullifiers = Vec::with_capacity(num_notes as usize); - - // iterate over the notes and extract the nullifier and script root - let mut note_ptr = 1; - while note_ptr < source.len() { - // make sure there is enough data to read (note data is well formed) - if note_ptr + 5 * WORD_SIZE > source.len() { - return Err(TransactionError::InvalidInputNoteDataLength); - } - - // compute the nullifier and extract script root and number of assets - let (nullifier, num_assets) = extract_note_data(&source[note_ptr..]); - - // push the [ConsumedNoteInfo] to the vector - nullifiers.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; - - // increment note pointer - note_ptr += (num_assets as usize * WORD_SIZE) + 30; - } - - Ok(nullifiers) - } -} - -// HELPERS -// ================================================================================================ - -/// Extracts and returns the nullifier and the number of assets from the provided note data. -/// -/// Expects the note data to be organized as follows: -/// [CN_SN, CN_SR, CN_IR, CN_VR, CN_M] -/// -/// - CN_SN is the serial number of the consumed note. -/// - CN_SR is the script root of the consumed note. -/// - CN_IR is the inputs root of the consumed note. -/// - CN_VR is the vault root of the consumed note. -/// - CN1_M is the metadata of the consumed note. -fn extract_note_data(note_data: &[Felt]) -> (Digest, u64) { - // compute the nullifier - let nullifier = Hasher::hash_elements(¬e_data[..4 * WORD_SIZE]); - - // extract the number of assets - let num_assets = note_data[4 * WORD_SIZE].as_int(); - - (nullifier, num_assets) } // ADVICE MAP diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index 8a55ce1a4..bee8ef56c 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -219,7 +219,7 @@ fn build_executed_transaction( let (advice_witness, _, map, store) = advice_provider.finalize(); // parse transaction results - let tx_outputs = TransactionKernel::parse_outputs(&stack_outputs, &map.into()) + let tx_outputs = TransactionKernel::parse_transaction_outputs(&stack_outputs, &map.into()) .map_err(TransactionExecutorError::TransactionResultError)?; let final_account = &tx_outputs.account; diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index a1fcfa568..8646cd372 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -1,15 +1,18 @@ use miden_lib::transaction::{ToTransactionKernelInputs, TransactionKernel}; -use miden_objects::transaction::{InputNotes, ProvenTransaction, TransactionWitness}; +use miden_objects::{ + notes::Nullifier, + transaction::{InputNotes, ProvenTransaction, TransactionWitness}, +}; use miden_prover::prove; pub use miden_prover::ProvingOptions; use vm_processor::MemAdviceProvider; use super::{TransactionHost, TransactionProverError}; -/// The [TransactionProver] is a stateless component which is responsible for proving transactions. +/// Transaction prover is a stateless component which is responsible for proving transactions. /// -/// The [TransactionProver] exposes the `prove_transaction` method which takes a [TransactionWitness] and -/// produces a [ProvenTransaction]. +/// Transaction prover exposes the `prove_transaction` method which takes a [TransactionWitness], +/// or anything that can be converted into a [TransactionWitness], and returns a [ProvenTransaction]. pub struct TransactionProver { proof_options: ProvingOptions, } @@ -25,7 +28,7 @@ impl TransactionProver { // TRANSACTION PROVER // -------------------------------------------------------------------------------------------- - /// Proves the provided [TransactionWitness] and returns a [ProvenTransaction]. + /// Proves the provided transaction and returns a [ProvenTransaction]. /// /// # Errors /// - If the consumed note data in the transaction witness is corrupt. @@ -40,19 +43,12 @@ impl TransactionProver { // extract required data from the transaction witness let (stack_inputs, advice_inputs) = tx_witness.get_kernel_inputs(); - let input_notes = match tx_witness.input_note_data() { - Some(input_note_data) => { - let nullifiers = - TransactionKernel::read_input_nullifiers_from(input_note_data).unwrap(); - InputNotes::new(nullifiers).unwrap() - }, - None => InputNotes::default(), - }; + let input_notes: InputNotes = (&tx_witness.tx_inputs().input_notes).into(); - let account_id = tx_witness.account_id(); - let initial_account_hash = tx_witness.initial_account_hash(); - let block_hash = tx_witness.block_hash(); - let tx_script_root = tx_witness.tx_script_root(); + let account_id = tx_witness.account().id(); + let initial_account_hash = tx_witness.account().hash(); + let block_hash = tx_witness.block_header().hash(); + let tx_script_root = tx_witness.tx_script().map(|script| *script.hash()); let advice_provider: MemAdviceProvider = advice_inputs.into(); let mut host = TransactionHost::new(advice_provider); @@ -63,7 +59,7 @@ impl TransactionProver { // extract transaction outputs and process transaction data let (advice_provider, _event_handler) = host.into_parts(); let (_, map, _) = advice_provider.into_parts(); - let tx_outputs = TransactionKernel::parse_outputs(&stack_outputs, &map.into()) + let tx_outputs = TransactionKernel::parse_transaction_outputs(&stack_outputs, &map.into()) .map_err(TransactionProverError::TransactionResultError)?; Ok(ProvenTransaction::new( diff --git a/miden-tx/src/tests.rs b/miden-tx/src/tests.rs index 2693185a9..3ee68e0f5 100644 --- a/miden-tx/src/tests.rs +++ b/miden-tx/src/tests.rs @@ -57,7 +57,8 @@ fn test_transaction_executor_witness() { let (advice_provider, _event_handler) = host.into_parts(); let (_, map, _) = advice_provider.into_parts(); - let tx_outputs = TransactionKernel::parse_outputs(result.stack_outputs(), &map.into()).unwrap(); + let tx_outputs = + TransactionKernel::parse_transaction_outputs(result.stack_outputs(), &map.into()).unwrap(); assert_eq!(executed_transaction.final_account().hash(), tx_outputs.account.hash()); assert_eq!(executed_transaction.output_notes(), &tx_outputs.output_notes); diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index 3045c8f05..248bc88ee 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -1,13 +1,11 @@ use core::cell::OnceCell; use super::{ - AdviceInputs, InputNotes, OutputNotes, Program, TransactionId, TransactionInputs, - TransactionOutputs, TransactionScript, TransactionWitness, -}; -use crate::{ - accounts::{Account, AccountDelta, AccountId, AccountStub}, - BlockHeader, TransactionError, + Account, AccountDelta, AccountId, AccountStub, AdviceInputs, BlockHeader, InputNotes, + OutputNotes, Program, TransactionId, TransactionInputs, TransactionOutputs, TransactionScript, + TransactionWitness, }; +use crate::TransactionError; // EXECUTED TRANSACTION // ================================================================================================ @@ -17,7 +15,7 @@ use crate::{ /// Executed transaction serves two primary purposes: /// - It contains a complete description of the effects of the transaction. Specifically, it /// contains all output notes created as the result of the transaction and describes all the -/// changes make to the involved account (i.e., the account delta). +/// changes made to the involved account (i.e., the account delta). /// - It contains all the information required to re-execute and prove the transaction in a /// stateless manner. This includes all public transaction inputs, but also all nondeterministic /// inputs that the host provided to Miden VM while executing the transaction (i.e., advice @@ -131,18 +129,32 @@ impl ExecutedTransaction { pub fn tx_inputs(&self) -> &TransactionInputs { &self.tx_inputs } + + /// Returns all the data requested by the VM from the advice provider while executing the + /// transaction program. + pub fn advice_witness(&self) -> &AdviceInputs { + &self.advice_witness + } + + // CONVERSIONS + // -------------------------------------------------------------------------------------------- + + /// Returns individual components of this transaction. + pub fn into_parts(self) -> (AccountDelta, TransactionOutputs, TransactionWitness) { + let tx_witness = TransactionWitness::new( + self.program, + self.tx_inputs, + self.tx_script, + self.advice_witness, + ); + + (self.account_delta, self.tx_outputs, tx_witness) + } } impl From for TransactionWitness { fn from(tx: ExecutedTransaction) -> Self { - Self::new( - tx.initial_account().id(), - tx.initial_account().hash(), - tx.block_header().hash(), - tx.input_notes().commitment(), - tx.tx_script().map(|s| *s.hash()), - tx.program, - tx.advice_witness, - ) + let (_, _, tx_witness) = tx.into_parts(); + tx_witness } } diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index df583e478..3c95bf2b1 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -77,6 +77,15 @@ impl From for InputNotes { } } +impl From<&InputNotes> for InputNotes { + fn from(value: &InputNotes) -> Self { + Self { + notes: value.notes.iter().map(|note| note.nullifier()).collect(), + commitment: OnceCell::new(), + } + } +} + // INPUT NOTES // ================================================================================================ diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index 2d6e0500b..815c49978 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -1,5 +1,5 @@ use super::{ - accounts::{Account, AccountId}, + accounts::{Account, AccountDelta, AccountId, AccountStub}, notes::{NoteEnvelope, Nullifier}, vm::{AdviceInputs, Program}, BlockHeader, Digest, Felt, Hasher, Word, WORD_SIZE, ZERO, diff --git a/objects/src/transaction/prepared_tx.rs b/objects/src/transaction/prepared_tx.rs index 7a32342b4..4e637865a 100644 --- a/objects/src/transaction/prepared_tx.rs +++ b/objects/src/transaction/prepared_tx.rs @@ -1,6 +1,4 @@ -use super::{ - Account, BlockHeader, ChainMmr, InputNotes, Program, TransactionInputs, TransactionScript, -}; +use super::{Account, BlockHeader, InputNotes, Program, TransactionInputs, TransactionScript}; use crate::TransactionError; // PREPARED TRANSACTION @@ -45,22 +43,17 @@ impl PreparedTransaction { &self.program } - /// Returns the account. + /// Returns the account for this transaction. pub fn account(&self) -> &Account { &self.tx_inputs.account } - /// Returns the block header. + /// Returns the block header for this transaction. pub fn block_header(&self) -> &BlockHeader { &self.tx_inputs.block_header } - /// Returns the block chain. - pub fn block_chain(&self) -> &ChainMmr { - &self.tx_inputs.block_chain - } - - /// Returns the input notes. + /// Returns the notes to be consumed in this transaction. pub fn input_notes(&self) -> &InputNotes { &self.tx_inputs.input_notes } diff --git a/objects/src/transaction/tx_witness.rs b/objects/src/transaction/tx_witness.rs index 51e509604..bc6d3ca88 100644 --- a/objects/src/transaction/tx_witness.rs +++ b/objects/src/transaction/tx_witness.rs @@ -1,27 +1,31 @@ -use super::{AccountId, AdviceInputs, Digest, Felt, Program}; +use super::{ + Account, AdviceInputs, BlockHeader, InputNotes, Program, TransactionInputs, TransactionScript, +}; // TRANSACTION WITNESS // ================================================================================================ -/// A [TransactionWitness] is the minimum required data required to execute and prove a Miden rollup +/// Transaction witness contains all the data required to execute and prove a Miden rollup /// transaction. /// -/// The [TransactionWitness] is composed of: -/// - account_id: the account id of the account the transaction is being executed against. -/// - 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. -/// - 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 +/// The main purpose of the transaction witness is to enable stateless re-execution and proving +/// of transactions. +/// +/// A transaction witness consists of: +/// - The executable transaction [Program]. +/// - Transaction inputs which contain information about the initial state of the account, input +/// notes, block header etc. +/// - An optional transaction script. +/// - Advice witness which contains all data requested by the VM from the advice provider while +/// executing the transaction program. +/// +/// TODO: currently, the advice witness contains redundant and irrelevant data (e.g., tx inputs +/// and tx outputs). we should optimize it to contain only the minimum data required for +/// executing/proving the transaction. pub struct TransactionWitness { - account_id: AccountId, - initial_account_hash: Digest, - block_hash: Digest, - input_notes_hash: Digest, - tx_script_root: Option, program: Program, + tx_inputs: TransactionInputs, + tx_script: Option, advice_witness: AdviceInputs, } @@ -30,21 +34,15 @@ impl TransactionWitness { // -------------------------------------------------------------------------------------------- /// Creates a new [TransactionWitness] from the provided data. pub fn new( - account_id: AccountId, - initial_account_hash: Digest, - block_hash: Digest, - input_notes_hash: Digest, - tx_script_root: Option, program: Program, + tx_inputs: TransactionInputs, + tx_script: Option, advice_witness: AdviceInputs, ) -> Self { Self { - account_id, - initial_account_hash, - block_hash, - input_notes_hash, - tx_script_root, program, + tx_inputs, + tx_script, advice_witness, } } @@ -52,46 +50,39 @@ impl TransactionWitness { // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns the account id of the account the transaction is executed against. - pub fn account_id(&self) -> AccountId { - self.account_id + /// Returns a reference the program defining this transaction. + pub fn program(&self) -> &Program { + &self.program } - /// Returns the initial account hash of the account the transaction is executed against. - pub fn initial_account_hash(&self) -> Digest { - self.initial_account_hash - } - /// Returns a commitment to the notes consumed by the transaction. - pub fn input_notes_hash(&self) -> Digest { - self.input_notes_hash + /// Returns the account state before the transaction was executed. + pub fn account(&self) -> &Account { + &self.tx_inputs.account } - /// Returns the block hash of the latest known block. - pub fn block_hash(&self) -> Digest { - self.block_hash + /// Returns the notes consumed in this transaction. + pub fn input_notes(&self) -> &InputNotes { + &self.tx_inputs.input_notes } - /// Returns the transaction script root. - pub fn tx_script_root(&self) -> Option { - self.tx_script_root + /// Returns the block header for the block against which the transaction was executed. + pub fn block_header(&self) -> &BlockHeader { + &self.tx_inputs.block_header } - /// Returns the transaction [Program]. - pub fn program(&self) -> &Program { - &self.program + /// Returns a reference to the transaction script. + pub fn tx_script(&self) -> Option<&TransactionScript> { + self.tx_script.as_ref() } - /// Returns the advice inputs for the transaction. - pub fn advice_inputs(&self) -> &AdviceInputs { - &self.advice_witness + /// Returns a reference to the inputs for this transaction. + pub fn tx_inputs(&self) -> &TransactionInputs { + &self.tx_inputs } - // ADVICE DATA EXTRACTORS - // -------------------------------------------------------------------------------------------- - - /// Returns data from the advice map located under `self.input_notes_hash` key. - pub fn input_note_data(&self) -> Option<&[Felt]> { - // TODO: return None if input_notes_hash == EMPTY_WORD? - self.advice_witness.mapped_values(&self.input_notes_hash.as_bytes()) + /// Returns all the data requested by the VM from the advice provider while executing the + /// transaction program. + pub fn advice_witness(&self) -> &AdviceInputs { + &self.advice_witness } } From 03491b7cbd79f5278f691bc22fac2f94aaddbfbf Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Mon, 1 Jan 2024 22:04:26 -0800 Subject: [PATCH 19/21] refactor: delete TransactionWitnessError --- miden-lib/src/transaction/mod.rs | 17 ++++++----- miden-tx/src/error.rs | 10 ++----- miden-tx/src/executor/mod.rs | 10 +++---- miden-tx/src/prover/mod.rs | 2 +- objects/src/errors.rs | 40 +++++--------------------- objects/src/lib.rs | 2 +- objects/src/transaction/executed_tx.rs | 2 +- objects/src/transaction/inputs.rs | 2 +- objects/src/transaction/outputs.rs | 8 +++--- 9 files changed, 31 insertions(+), 62 deletions(-) diff --git a/miden-lib/src/transaction/mod.rs b/miden-lib/src/transaction/mod.rs index 427f14eae..5d5d0dbec 100644 --- a/miden-lib/src/transaction/mod.rs +++ b/miden-lib/src/transaction/mod.rs @@ -7,7 +7,7 @@ use miden_objects::{ group_slice_elements, }, vm::{ProgramInfo, StackInputs, StackOutputs}, - Digest, Felt, TransactionResultError, Word, + Digest, Felt, TransactionOutputError, Word, }; use miden_stdlib::StdLibrary; @@ -161,39 +161,38 @@ impl TransactionKernel { pub fn parse_transaction_outputs( stack: &StackOutputs, adv_map: &AdviceMap, - ) -> Result { + ) -> Result { let (final_acct_hash, output_notes_hash, _tx_script_root) = Self::parse_output_stack(stack); // --- parse final account state -------------------------------------- let final_account_data: &[Word] = group_slice_elements( adv_map .get(final_acct_hash) - .ok_or(TransactionResultError::FinalAccountDataNotFound)?, + .ok_or(TransactionOutputError::FinalAccountDataNotFound)?, ); let account = parse_final_account_stub(final_account_data) - .map_err(TransactionResultError::FinalAccountStubDataInvalid)?; + .map_err(TransactionOutputError::FinalAccountStubDataInvalid)?; // --- parse output notes --------------------------------------------- let output_notes_data: &[Word] = group_slice_elements( adv_map .get(output_notes_hash) - .ok_or(TransactionResultError::OutputNoteDataNotFound)?, + .ok_or(TransactionOutputError::OutputNoteDataNotFound)?, ); let mut output_notes = Vec::new(); let mut output_note_ptr = 0; while output_note_ptr < output_notes_data.len() { let output_note = notes_try_from_elements(&output_notes_data[output_note_ptr..]) - .map_err(TransactionResultError::OutputNoteDataInvalid)?; + .map_err(TransactionOutputError::OutputNoteDataInvalid)?; output_notes.push(output_note); output_note_ptr += memory::NOTE_MEM_SIZE as usize; } - let output_notes = - OutputNotes::new(output_notes).map_err(TransactionResultError::OutputNotesError)?; + let output_notes = OutputNotes::new(output_notes)?; if output_notes_hash != output_notes.commitment() { - return Err(TransactionResultError::OutputNotesCommitmentInconsistent( + return Err(TransactionOutputError::OutputNotesCommitmentInconsistent( output_notes_hash, output_notes.commitment(), )); diff --git a/miden-tx/src/error.rs b/miden-tx/src/error.rs index f6cafb32e..f3c256b74 100644 --- a/miden-tx/src/error.rs +++ b/miden-tx/src/error.rs @@ -1,9 +1,6 @@ use core::fmt; -use miden_objects::{ - assembly::AssemblyError, crypto::merkle::NodeIndex, TransactionResultError, - TransactionWitnessError, -}; +use miden_objects::{assembly::AssemblyError, crypto::merkle::NodeIndex, TransactionOutputError}; use miden_verifier::VerificationError; use super::{AccountError, AccountId, Digest, ExecutionError}; @@ -46,7 +43,7 @@ pub enum TransactionExecutorError { FetchAccountCodeFailed(DataStoreError), FetchTransactionInputsFailed(DataStoreError), LoadAccountFailed(TransactionCompilerError), - TransactionResultError(TransactionResultError), + TransactionOutputError(TransactionOutputError), } impl fmt::Display for TransactionExecutorError { @@ -63,8 +60,7 @@ impl std::error::Error for TransactionExecutorError {} #[derive(Debug)] pub enum TransactionProverError { ProveTransactionProgramFailed(ExecutionError), - TransactionResultError(TransactionResultError), - CorruptTransactionWitnessConsumedNoteData(TransactionWitnessError), + TransactionOutputError(TransactionOutputError), } impl fmt::Display for TransactionProverError { diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index bee8ef56c..bb5ebb6f5 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -5,7 +5,7 @@ use miden_objects::{ crypto::merkle::{merkle_tree_delta, MerkleStore}, transaction::{TransactionInputs, TransactionScript}, vm::{Program, StackOutputs}, - Felt, TransactionResultError, Word, + Felt, TransactionOutputError, Word, }; use vm_processor::ExecutionOptions; @@ -220,7 +220,7 @@ fn build_executed_transaction( // parse transaction results let tx_outputs = TransactionKernel::parse_transaction_outputs(&stack_outputs, &map.into()) - .map_err(TransactionExecutorError::TransactionResultError)?; + .map_err(TransactionExecutorError::TransactionOutputError)?; let final_account = &tx_outputs.account; let initial_account = &tx_inputs.account; @@ -230,7 +230,7 @@ fn build_executed_transaction( // TODO: Fix delta extraction for new account creation // extract the account storage delta let storage_delta = extract_account_storage_delta(&store, initial_account, final_account) - .map_err(TransactionExecutorError::TransactionResultError)?; + .map_err(TransactionExecutorError::TransactionOutputError)?; // extract the nonce delta let nonce_delta = if initial_account.nonce() != final_account.nonce() { @@ -263,7 +263,7 @@ fn extract_account_storage_delta( store: &MerkleStore, initial_account: &Account, final_account_stub: &AccountStub, -) -> Result { +) -> Result { // extract storage slots delta let tree_delta = merkle_tree_delta( initial_account.storage().root(), @@ -271,7 +271,7 @@ fn extract_account_storage_delta( AccountStorage::STORAGE_TREE_DEPTH, store, ) - .map_err(TransactionResultError::ExtractAccountStorageSlotsDeltaFailed)?; + .map_err(TransactionOutputError::ExtractAccountStorageSlotsDeltaFailed)?; // map tree delta to cleared/updated slots; we can cast indexes to u8 because the // the number of storage slots cannot be greater than 256 diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 8646cd372..42b3a53da 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -60,7 +60,7 @@ impl TransactionProver { let (advice_provider, _event_handler) = host.into_parts(); let (_, map, _) = advice_provider.into_parts(); let tx_outputs = TransactionKernel::parse_transaction_outputs(&stack_outputs, &map.into()) - .map_err(TransactionProverError::TransactionResultError)?; + .map_err(TransactionProverError::TransactionOutputError)?; Ok(ProvenTransaction::new( account_id, diff --git a/objects/src/errors.rs b/objects/src/errors.rs index 887778b1f..fed808165 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -274,25 +274,19 @@ impl std::error::Error for ChainMmrError {} #[derive(Debug, Clone)] pub enum TransactionError { - AccountSeedNoteProvidedForNewAccount, + AccountSeedNotProvidedForNewAccount, AccountSeedProvidedForExistingAccount, DuplicateInputNote(Digest), - DuplicateOutputNote(Digest), InconsistentAccountId { input_id: AccountId, output_id: AccountId, }, InvalidAccountSeed(AccountError), - InvalidInputNoteDataLength, ScriptCompilationError(AssemblyError), TooManyInputNotes { max: usize, actual: usize, }, - TooManyOutputNotes { - max: usize, - actual: usize, - }, } impl fmt::Display for TransactionError { @@ -304,46 +298,26 @@ impl fmt::Display for TransactionError { #[cfg(feature = "std")] impl std::error::Error for TransactionError {} -// TRANSACTION RESULT ERROR +// TRANSACTION OUTPUT ERROR // =============================================================================================== #[derive(Debug, Clone)] -pub enum TransactionResultError { +pub enum TransactionOutputError { + DuplicateOutputNote(Digest), ExtractAccountStorageSlotsDeltaFailed(MerkleError), - ExtractAccountStorageStoreDeltaFailed(MerkleError), - ExtractAccountVaultLeavesDeltaFailed(MerkleError), FinalAccountDataNotFound, FinalAccountStubDataInvalid(AccountError), - InconsistentAccountCodeHash(Digest, Digest), OutputNoteDataNotFound, OutputNoteDataInvalid(NoteError), OutputNotesCommitmentInconsistent(Digest, Digest), - OutputNotesError(TransactionError), - UpdatedAccountCodeInvalid(AccountError), -} - -impl fmt::Display for TransactionResultError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for TransactionResultError {} - -// TRANSACTION WITNESS ERROR -// ================================================================================================ -#[derive(Debug)] -pub enum TransactionWitnessError { - ConsumedNoteDataNotFound, - InvalidConsumedNoteDataLength, + TooManyOutputNotes { max: usize, actual: usize }, } -impl fmt::Display for TransactionWitnessError { +impl fmt::Display for TransactionOutputError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } } #[cfg(feature = "std")] -impl std::error::Error for TransactionWitnessError {} +impl std::error::Error for TransactionOutputError {} diff --git a/objects/src/lib.rs b/objects/src/lib.rs index 04989b545..722a92c5c 100644 --- a/objects/src/lib.rs +++ b/objects/src/lib.rs @@ -17,7 +17,7 @@ pub mod transaction; mod errors; pub use errors::{ AccountDeltaError, AccountError, AssetError, ChainMmrError, NoteError, TransactionError, - TransactionResultError, TransactionWitnessError, + TransactionOutputError, }; // RE-EXPORTS // ================================================================================================ diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index 248bc88ee..2cb573406 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -51,7 +51,7 @@ impl ExecutedTransaction { advice_witness: AdviceInputs, ) -> Result { // make sure account IDs are consistent across transaction inputs and outputs - if tx_inputs.account.id() != tx_inputs.account.id() { + if tx_inputs.account.id() != tx_outputs.account.id() { return Err(TransactionError::InconsistentAccountId { input_id: tx_inputs.account.id(), output_id: tx_outputs.account.id(), diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index 3c95bf2b1..f5373a2b3 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -36,7 +36,7 @@ impl TransactionInputs { match (self.account.is_new(), self.account_seed) { (true, Some(seed)) => validate_account_seed(&self.account, seed) .map_err(TransactionError::InvalidAccountSeed), - (true, None) => Err(TransactionError::AccountSeedNoteProvidedForNewAccount), + (true, None) => Err(TransactionError::AccountSeedNotProvidedForNewAccount), (false, Some(_)) => Err(TransactionError::AccountSeedProvidedForExistingAccount), (false, None) => Ok(()), } diff --git a/objects/src/transaction/outputs.rs b/objects/src/transaction/outputs.rs index eabfc5405..909d14c89 100644 --- a/objects/src/transaction/outputs.rs +++ b/objects/src/transaction/outputs.rs @@ -9,7 +9,7 @@ use crate::{ serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, string::ToString, }, - Digest, Felt, Hasher, StarkField, TransactionError, Word, + Digest, Felt, Hasher, StarkField, TransactionOutputError, Word, }; // TRANSACTION OUTPUTS @@ -89,9 +89,9 @@ impl OutputNotes { /// 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 { + pub fn new(notes: Vec) -> Result { if notes.len() > MAX_OUTPUT_NOTES_PER_TRANSACTION { - return Err(TransactionError::TooManyOutputNotes { + return Err(TransactionOutputError::TooManyOutputNotes { max: MAX_OUTPUT_NOTES_PER_TRANSACTION, actual: notes.len(), }); @@ -100,7 +100,7 @@ impl OutputNotes { let mut seen_notes = BTreeSet::new(); for note in notes.iter() { if !seen_notes.insert(note.hash()) { - return Err(TransactionError::DuplicateOutputNote(note.hash())); + return Err(TransactionOutputError::DuplicateOutputNote(note.hash())); } } From 1949366d83c62276d81b4bdf977bdf629fc43993 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 2 Jan 2024 01:27:30 -0800 Subject: [PATCH 20/21] refactor: replace TransactionError with TransactionInputError --- miden-lib/src/tests/test_prologue.rs | 4 +- miden-lib/src/transaction/inputs.rs | 10 ++-- miden-tx/src/compiler/mod.rs | 79 +++++++++++++++----------- miden-tx/src/error.rs | 29 +++++----- miden-tx/src/executor/mod.rs | 25 ++++---- miden-tx/src/prover/mod.rs | 4 +- miden-tx/src/tests.rs | 15 ++--- miden-tx/tests/common/mod.rs | 15 ++--- mock/src/lib.rs | 11 ++-- mock/src/mock/transaction.rs | 12 ++-- objects/src/errors.rs | 35 +++++++----- objects/src/lib.rs | 4 +- objects/src/transaction/executed_tx.rs | 30 +++------- objects/src/transaction/inputs.rs | 79 ++++++++++++++++++++------ objects/src/transaction/prepared_tx.rs | 16 ++---- objects/src/transaction/tx_script.rs | 8 +-- objects/src/transaction/tx_witness.rs | 6 +- 17 files changed, 221 insertions(+), 161 deletions(-) diff --git a/miden-lib/src/tests/test_prologue.rs b/miden-lib/src/tests/test_prologue.rs index 23cada695..0f3e0510c 100644 --- a/miden-lib/src/tests/test_prologue.rs +++ b/miden-lib/src/tests/test_prologue.rs @@ -190,10 +190,10 @@ fn chain_mmr_memory_assertions( // The number of leaves should be stored at the CHAIN_MMR_NUM_LEAVES_PTR assert_eq!( process.get_mem_value(ContextId::root(), CHAIN_MMR_NUM_LEAVES_PTR).unwrap()[0], - Felt::new(inputs.tx_inputs().block_chain.chain_length() as u64) + Felt::new(inputs.tx_inputs().block_chain().chain_length() as u64) ); - for (i, peak) in inputs.tx_inputs().block_chain.peaks().peaks().iter().enumerate() { + for (i, peak) in inputs.tx_inputs().block_chain().peaks().peaks().iter().enumerate() { // The peaks should be stored at the CHAIN_MMR_PEAKS_PTR let i: u32 = i.try_into().expect( "Number of peaks is log2(number_of_leaves), this value won't be larger than 2**32", diff --git a/miden-lib/src/transaction/inputs.rs b/miden-lib/src/transaction/inputs.rs index 56ab58a71..68255a8a0 100644 --- a/miden-lib/src/transaction/inputs.rs +++ b/miden-lib/src/transaction/inputs.rs @@ -89,9 +89,9 @@ fn extend_advice_inputs( build_advice_stack(tx_inputs, tx_script, advice_inputs); // build the advice map and Merkle store for relevant components - add_chain_mmr_to_advice_inputs(&tx_inputs.block_chain, advice_inputs); - add_account_to_advice_inputs(&tx_inputs.account, tx_inputs.account_seed, advice_inputs); - add_input_notes_to_advice_inputs(&tx_inputs.input_notes, advice_inputs); + add_chain_mmr_to_advice_inputs(tx_inputs.block_chain(), advice_inputs); + add_account_to_advice_inputs(tx_inputs.account(), tx_inputs.account_seed(), advice_inputs); + add_input_notes_to_advice_inputs(tx_inputs.input_notes(), advice_inputs); add_tx_script_inputs_to_advice_map(tx_script, advice_inputs); } @@ -121,7 +121,7 @@ fn build_advice_stack( inputs: &mut AdviceInputs, ) { // push block header info into the stack - let header = &tx_inputs.block_header; + let header = tx_inputs.block_header(); inputs.extend_stack(header.prev_hash()); inputs.extend_stack(header.chain_root()); inputs.extend_stack(header.account_root()); @@ -133,7 +133,7 @@ fn build_advice_stack( inputs.extend_stack(header.note_root()); // push core account items onto the stack - let account = &tx_inputs.account; + let account = tx_inputs.account(); inputs.extend_stack([account.id().into(), ZERO, ZERO, account.nonce()]); inputs.extend_stack(account.vault().commitment()); inputs.extend_stack(account.storage().root()); diff --git a/miden-tx/src/compiler/mod.rs b/miden-tx/src/compiler/mod.rs index 0ec3bf5b2..af266d676 100644 --- a/miden-tx/src/compiler/mod.rs +++ b/miden-tx/src/compiler/mod.rs @@ -1,7 +1,7 @@ use miden_objects::{ assembly::{Assembler, AssemblyContext, ModuleAst, ProgramAst}, transaction::{InputNotes, TransactionScript}, - Felt, TransactionError, Word, + Felt, NoteError, TransactionScriptError, Word, }; use super::{ @@ -82,16 +82,19 @@ impl TransactionCompiler { note_script_ast: ProgramAst, target_account_proc: Vec, ) -> Result { - let (note_script, code_block) = NoteScript::new(note_script_ast, &self.assembler) - .map_err(|_| TransactionCompilerError::CompileNoteScriptFailed)?; + let (note_script, code_block) = + NoteScript::new(note_script_ast, &self.assembler).map_err(|err| match err { + NoteError::ScriptCompilationError(err) => { + TransactionCompilerError::CompileNoteScriptFailed(err) + }, + _ => TransactionCompilerError::NoteScriptError(err), + })?; for note_target in target_account_proc.into_iter() { verify_program_account_compatibility( &code_block, &self.get_target_interface(note_target)?, - ) - .map_err(|_| { - TransactionCompilerError::NoteIncompatibleWithAccountInterface(code_block.hash()) - })?; + ScriptType::NoteScript, + )?; } Ok(note_script) @@ -111,19 +114,17 @@ impl TransactionCompiler { let (tx_script, code_block) = TransactionScript::new(tx_script_ast, tx_script_inputs, &mut self.assembler).map_err( |e| match e { - TransactionError::ScriptCompilationError(asm_error) => { + TransactionScriptError::ScriptCompilationError(asm_error) => { TransactionCompilerError::CompileTxScriptFailed(asm_error) }, - _ => TransactionCompilerError::CompileTxScriptFailedUnknown, }, )?; for target in target_account_proc.into_iter() { - verify_program_account_compatibility(&code_block, &self.get_target_interface(target)?) - .map_err(|_| { - TransactionCompilerError::TxScriptIncompatibleWithAccountInterface( - code_block.hash(), - ) - })?; + verify_program_account_compatibility( + &code_block, + &self.get_target_interface(target)?, + ScriptType::TransactionScript, + )?; } Ok(tx_script) } @@ -150,7 +151,7 @@ impl TransactionCompiler { // Transaction must contain at least one input note or a transaction script if notes.is_empty() && tx_script.is_none() { - return Err(TransactionCompilerError::InvalidTransactionInputs); + return Err(TransactionCompilerError::NoTransactionDriver); } // Create the [AssemblyContext] for compilation of notes scripts and the transaction script @@ -216,13 +217,11 @@ impl TransactionCompiler { let note_program = self .assembler .compile_in_context(recorded_note.note().script().code(), assembly_context) - .map_err(|_| TransactionCompilerError::CompileNoteScriptFailed)?; - verify_program_account_compatibility(¬e_program, target_account_interface).map_err( - |_| { - TransactionCompilerError::NoteIncompatibleWithAccountInterface( - note_program.hash(), - ) - }, + .map_err(TransactionCompilerError::CompileNoteScriptFailed)?; + verify_program_account_compatibility( + ¬e_program, + target_account_interface, + ScriptType::NoteScript, )?; note_programs.push(note_program); } @@ -243,12 +242,11 @@ impl TransactionCompiler { .assembler .compile_in_context(tx_script, assembly_context) .map_err(TransactionCompilerError::CompileTxScriptFailed)?; - verify_program_account_compatibility(&tx_script_code_block, &target_account_interface) - .map_err(|_| { - TransactionCompilerError::TxScriptIncompatibleWithAccountInterface( - tx_script_code_block.hash(), - ) - })?; + verify_program_account_compatibility( + &tx_script_code_block, + &target_account_interface, + ScriptType::TransactionScript, + )?; Ok(tx_script_code_block) } @@ -282,14 +280,16 @@ impl Default for TransactionCompiler { // ------------------------------------------------------------------------------------------------ /// Verifies that the provided program is compatible with the target account interface. +/// /// This is achieved by checking that at least one execution branch in the program is compatible /// with the target account interface. /// /// # Errors -/// Returns an error if the note script is not compatible with the target account interface. +/// Returns an error if the program is not compatible with the target account interface. fn verify_program_account_compatibility( program: &CodeBlock, target_account_interface: &[Digest], + script_type: ScriptType, ) -> Result<(), TransactionCompilerError> { // collect call branches let branches = collect_call_branches(program); @@ -298,9 +298,14 @@ fn verify_program_account_compatibility( if !branches.iter().any(|call_targets| { call_targets.iter().all(|target| target_account_interface.contains(target)) }) { - return Err(TransactionCompilerError::ProgramIncompatibleWithAccountInterface( - program.hash(), - )); + return match script_type { + ScriptType::NoteScript => { + Err(TransactionCompilerError::NoteIncompatibleWithAccountInterface(program.hash())) + }, + ScriptType::TransactionScript => Err( + TransactionCompilerError::TxScriptIncompatibleWithAccountInterface(program.hash()), + ), + }; } Ok(()) @@ -366,3 +371,11 @@ pub enum ScriptTarget { AccountId(AccountId), Procedures(Vec), } + +// SCRIPT TYPE +// ================================================================================================ + +enum ScriptType { + NoteScript, + TransactionScript, +} diff --git a/miden-tx/src/error.rs b/miden-tx/src/error.rs index f3c256b74..ed12e36b3 100644 --- a/miden-tx/src/error.rs +++ b/miden-tx/src/error.rs @@ -1,6 +1,8 @@ use core::fmt; -use miden_objects::{assembly::AssemblyError, crypto::merkle::NodeIndex, TransactionOutputError}; +use miden_objects::{ + assembly::AssemblyError, crypto::merkle::NodeIndex, NoteError, TransactionOutputError, +}; use miden_verifier::VerificationError; use super::{AccountError, AccountId, Digest, ExecutionError}; @@ -9,16 +11,15 @@ use super::{AccountError, AccountId, Digest, ExecutionError}; // ================================================================================================ #[derive(Debug)] pub enum TransactionCompilerError { - InvalidTransactionInputs, - LoadAccountFailed(AccountError), AccountInterfaceNotFound(AccountId), - ProgramIncompatibleWithAccountInterface(Digest), + BuildCodeBlockTableFailed(AssemblyError), + CompileNoteScriptFailed(AssemblyError), + CompileTxScriptFailed(AssemblyError), + LoadAccountFailed(AccountError), NoteIncompatibleWithAccountInterface(Digest), + NoteScriptError(NoteError), + NoTransactionDriver, TxScriptIncompatibleWithAccountInterface(Digest), - CompileNoteScriptFailed, - CompileTxScriptFailed(AssemblyError), - CompileTxScriptFailedUnknown, - BuildCodeBlockTableFailed(AssemblyError), } impl fmt::Display for TransactionCompilerError { @@ -36,14 +37,16 @@ impl std::error::Error for TransactionCompilerError {} pub enum TransactionExecutorError { CompileNoteScriptFailed(TransactionCompilerError), CompileTransactionScriptFailed(TransactionCompilerError), - CompileTransactionError(TransactionCompilerError), - ConstructPreparedTransactionFailed(miden_objects::TransactionError), + CompileTransactionFiled(TransactionCompilerError), ExecuteTransactionProgramFailed(ExecutionError), - ExecutedTransactionConstructionFailed(miden_objects::TransactionError), FetchAccountCodeFailed(DataStoreError), FetchTransactionInputsFailed(DataStoreError), + InconsistentAccountId { + input_id: AccountId, + output_id: AccountId, + }, LoadAccountFailed(TransactionCompilerError), - TransactionOutputError(TransactionOutputError), + OutputConstructionFailed(TransactionOutputError), } impl fmt::Display for TransactionExecutorError { @@ -60,7 +63,7 @@ impl std::error::Error for TransactionExecutorError {} #[derive(Debug)] pub enum TransactionProverError { ProveTransactionProgramFailed(ExecutionError), - TransactionOutputError(TransactionOutputError), + OutputConstructionFailed(TransactionOutputError), } impl fmt::Display for TransactionProverError { diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index bb5ebb6f5..4aeb49213 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -193,13 +193,12 @@ impl TransactionExecutor { .compiler .compile_transaction( account_id, - &tx_inputs.input_notes, + tx_inputs.input_notes(), tx_script.as_ref().map(|x| x.code()), ) - .map_err(TransactionExecutorError::CompileTransactionError)?; + .map_err(TransactionExecutorError::CompileTransactionFiled)?; - PreparedTransaction::new(tx_program, tx_script, tx_inputs) - .map_err(TransactionExecutorError::ConstructPreparedTransactionFailed) + Ok(PreparedTransaction::new(tx_program, tx_script, tx_inputs)) } } @@ -220,17 +219,24 @@ fn build_executed_transaction( // parse transaction results let tx_outputs = TransactionKernel::parse_transaction_outputs(&stack_outputs, &map.into()) - .map_err(TransactionExecutorError::TransactionOutputError)?; + .map_err(TransactionExecutorError::OutputConstructionFailed)?; let final_account = &tx_outputs.account; - let initial_account = &tx_inputs.account; + let initial_account = tx_inputs.account(); + + if initial_account.id() != final_account.id() { + return Err(TransactionExecutorError::InconsistentAccountId { + input_id: initial_account.id(), + output_id: final_account.id(), + }); + } // build account delta // TODO: Fix delta extraction for new account creation // extract the account storage delta let storage_delta = extract_account_storage_delta(&store, initial_account, final_account) - .map_err(TransactionExecutorError::TransactionOutputError)?; + .map_err(TransactionExecutorError::OutputConstructionFailed)?; // extract the nonce delta let nonce_delta = if initial_account.nonce() != final_account.nonce() { @@ -246,15 +252,14 @@ fn build_executed_transaction( let account_delta = AccountDelta::new(storage_delta, vault_delta, nonce_delta).expect("invalid account delta"); - ExecutedTransaction::new( + Ok(ExecutedTransaction::new( program, tx_inputs, tx_outputs, account_delta, tx_script, advice_witness, - ) - .map_err(TransactionExecutorError::ExecutedTransactionConstructionFailed) + )) } /// Extracts account storage delta between the `initial_account` and `final_account_stub` from the diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 42b3a53da..f139f6c05 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -43,7 +43,7 @@ impl TransactionProver { // extract required data from the transaction witness let (stack_inputs, advice_inputs) = tx_witness.get_kernel_inputs(); - let input_notes: InputNotes = (&tx_witness.tx_inputs().input_notes).into(); + let input_notes: InputNotes = (tx_witness.tx_inputs().input_notes()).into(); let account_id = tx_witness.account().id(); let initial_account_hash = tx_witness.account().hash(); @@ -60,7 +60,7 @@ impl TransactionProver { let (advice_provider, _event_handler) = host.into_parts(); let (_, map, _) = advice_provider.into_parts(); let tx_outputs = TransactionKernel::parse_transaction_outputs(&stack_outputs, &map.into()) - .map_err(TransactionProverError::TransactionOutputError)?; + .map_err(TransactionProverError::OutputConstructionFailed)?; Ok(ProvenTransaction::new( account_id, diff --git a/miden-tx/src/tests.rs b/miden-tx/src/tests.rs index 3ee68e0f5..489f7ad3b 100644 --- a/miden-tx/src/tests.rs +++ b/miden-tx/src/tests.rs @@ -380,13 +380,14 @@ impl DataStore for MockDataStore { assert_eq!(notes.len(), self.notes.len()); let origins = self.notes.iter().map(|note| note.origin()).collect::>(); notes.iter().all(|note| origins.contains(¬e)); - Ok(TransactionInputs { - account: self.account.clone(), - account_seed: None, - block_header: self.block_header, - block_chain: self.block_chain.clone(), - input_notes: InputNotes::new(self.notes.clone()).unwrap(), - }) + Ok(TransactionInputs::new( + self.account.clone(), + None, + self.block_header, + self.block_chain.clone(), + InputNotes::new(self.notes.clone()).unwrap(), + ) + .unwrap()) } fn get_account_code(&self, account_id: AccountId) -> Result { diff --git a/miden-tx/tests/common/mod.rs b/miden-tx/tests/common/mod.rs index a72af5645..dac1ec9bc 100644 --- a/miden-tx/tests/common/mod.rs +++ b/miden-tx/tests/common/mod.rs @@ -84,13 +84,14 @@ impl DataStore for MockDataStore { assert_eq!(notes.len(), self.notes.len()); let origins = self.notes.iter().map(|note| note.origin()).collect::>(); notes.iter().all(|note| origins.contains(¬e)); - Ok(TransactionInputs { - account: self.account.clone(), - account_seed: None, - block_header: self.block_header, - block_chain: self.block_chain.clone(), - input_notes: InputNotes::new(self.notes.clone()).unwrap(), - }) + Ok(TransactionInputs::new( + self.account.clone(), + None, + self.block_header, + self.block_chain.clone(), + InputNotes::new(self.notes.clone()).unwrap(), + ) + .unwrap()) } fn get_account_code(&self, account_id: AccountId) -> Result { diff --git a/mock/src/lib.rs b/mock/src/lib.rs index c164f5411..dad25d054 100644 --- a/mock/src/lib.rs +++ b/mock/src/lib.rs @@ -110,13 +110,14 @@ pub fn prepare_transaction( let program = assembler.compile(code).unwrap(); - let tx_inputs = TransactionInputs { + let tx_inputs = TransactionInputs::new( account, account_seed, block_header, - block_chain: chain, - input_notes: InputNotes::new(notes).unwrap(), - }; + chain, + InputNotes::new(notes).unwrap(), + ) + .unwrap(); - PreparedTransaction::new(program, tx_script, tx_inputs).unwrap() + PreparedTransaction::new(program, tx_script, tx_inputs) } diff --git a/mock/src/mock/transaction.rs b/mock/src/mock/transaction.rs index a67eb2305..5fc6eabe4 100644 --- a/mock/src/mock/transaction.rs +++ b/mock/src/mock/transaction.rs @@ -124,13 +124,14 @@ pub fn mock_executed_tx(asset_preservation: AssetPreservationStatus) -> Executed &[initial_account.clone()], ); - let tx_inputs = TransactionInputs { - account: initial_account, - account_seed: None, + let tx_inputs = TransactionInputs::new( + initial_account, + None, block_header, block_chain, - input_notes: InputNotes::new(input_notes).unwrap(), - }; + InputNotes::new(input_notes).unwrap(), + ) + .unwrap(); let tx_outputs = TransactionOutputs { account: final_account.into(), @@ -144,7 +145,6 @@ pub fn mock_executed_tx(asset_preservation: AssetPreservationStatus) -> Executed // Executed Transaction ExecutedTransaction::new(program, tx_inputs, tx_outputs, account_delta, None, advice_witness) - .unwrap() } // HELPER FUNCTIONS diff --git a/objects/src/errors.rs b/objects/src/errors.rs index fed808165..402ad2f88 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -269,34 +269,43 @@ impl fmt::Display for ChainMmrError { #[cfg(feature = "std")] impl std::error::Error for ChainMmrError {} -// TRANSACTION ERROR +// TRANSACTION SCRIPT ERROR // ================================================================================================ #[derive(Debug, Clone)] -pub enum TransactionError { +pub enum TransactionScriptError { + ScriptCompilationError(AssemblyError), +} + +impl fmt::Display for TransactionScriptError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TransactionScriptError {} + +// TRANSACTION INPUT ERROR +// ================================================================================================ + +#[derive(Debug, Clone)] +pub enum TransactionInputError { AccountSeedNotProvidedForNewAccount, AccountSeedProvidedForExistingAccount, DuplicateInputNote(Digest), - InconsistentAccountId { - input_id: AccountId, - output_id: AccountId, - }, InvalidAccountSeed(AccountError), - ScriptCompilationError(AssemblyError), - TooManyInputNotes { - max: usize, - actual: usize, - }, + TooManyInputNotes { max: usize, actual: usize }, } -impl fmt::Display for TransactionError { +impl fmt::Display for TransactionInputError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } } #[cfg(feature = "std")] -impl std::error::Error for TransactionError {} +impl std::error::Error for TransactionInputError {} // TRANSACTION OUTPUT ERROR // =============================================================================================== diff --git a/objects/src/lib.rs b/objects/src/lib.rs index 722a92c5c..3fd0bf1ce 100644 --- a/objects/src/lib.rs +++ b/objects/src/lib.rs @@ -16,8 +16,8 @@ pub mod transaction; mod errors; pub use errors::{ - AccountDeltaError, AccountError, AssetError, ChainMmrError, NoteError, TransactionError, - TransactionOutputError, + AccountDeltaError, AccountError, AssetError, ChainMmrError, NoteError, TransactionInputError, + TransactionOutputError, TransactionScriptError, }; // RE-EXPORTS // ================================================================================================ diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index 2cb573406..be2cc01af 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -5,7 +5,6 @@ use super::{ OutputNotes, Program, TransactionId, TransactionInputs, TransactionOutputs, TransactionScript, TransactionWitness, }; -use crate::TransactionError; // EXECUTED TRANSACTION // ================================================================================================ @@ -37,11 +36,8 @@ impl ExecutedTransaction { /// Returns a new [ExecutedTransaction] instantiated from the provided data. /// - /// # Errors - /// Returns an error if: - /// - Input and output account IDs are not the same. - /// - For a new account, account seed is not provided or the provided seed is invalid. - /// - For an existing account, account seed was provided. + /// # Panics + /// Panics if input and output account IDs are not the same. pub fn new( program: Program, tx_inputs: TransactionInputs, @@ -49,19 +45,11 @@ impl ExecutedTransaction { account_delta: AccountDelta, tx_script: Option, advice_witness: AdviceInputs, - ) -> Result { + ) -> Self { // make sure account IDs are consistent across transaction inputs and outputs - if tx_inputs.account.id() != tx_outputs.account.id() { - return Err(TransactionError::InconsistentAccountId { - input_id: tx_inputs.account.id(), - output_id: tx_outputs.account.id(), - }); - } - - // if this transaction was executed against a new account, validate the account seed - tx_inputs.validate_new_account_seed()?; + assert_eq!(tx_inputs.account().id(), tx_outputs.account.id()); - Ok(Self { + Self { id: OnceCell::new(), program, tx_inputs, @@ -69,7 +57,7 @@ impl ExecutedTransaction { account_delta, tx_script, advice_witness, - }) + } } // PUBLIC ACCESSORS @@ -92,7 +80,7 @@ impl ExecutedTransaction { /// Returns the description of the account before the transaction was executed. pub fn initial_account(&self) -> &Account { - &self.tx_inputs.account + self.tx_inputs.account() } /// Returns description of the account after the transaction was executed. @@ -102,7 +90,7 @@ impl ExecutedTransaction { /// Returns the notes consumed in this transaction. pub fn input_notes(&self) -> &InputNotes { - &self.tx_inputs.input_notes + self.tx_inputs.input_notes() } /// Returns the notes created in this transaction. @@ -117,7 +105,7 @@ impl ExecutedTransaction { /// Returns the block header for the block against which the transaction was executed. pub fn block_header(&self) -> &BlockHeader { - &self.tx_inputs.block_header + self.tx_inputs.block_header() } /// Returns a description of changes between the initial and final account states. diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index f5373a2b3..0cde12ee4 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -9,7 +9,7 @@ use crate::{ serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, string::ToString, }, - TransactionError, + TransactionInputError, }; // TRANSACTION INPUTS @@ -18,28 +18,73 @@ use crate::{ /// Contains the data required to execute a transaction. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TransactionInputs { - pub account: Account, - pub account_seed: Option, - pub block_header: BlockHeader, - pub block_chain: ChainMmr, - pub input_notes: InputNotes, + account: Account, + account_seed: Option, + block_header: BlockHeader, + block_chain: ChainMmr, + input_notes: InputNotes, } impl TransactionInputs { - /// Validates that a valid account seed has been provided for new accounts. + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + /// Returns new [TransactionInputs] instantiated with the specified parameters. /// /// # Errors /// Returns an error if: /// - For a new account, account seed is not provided or the provided seed is invalid. /// - For an existing account, account seed was provided. - pub fn validate_new_account_seed(&self) -> Result<(), TransactionError> { - match (self.account.is_new(), self.account_seed) { - (true, Some(seed)) => validate_account_seed(&self.account, seed) - .map_err(TransactionError::InvalidAccountSeed), - (true, None) => Err(TransactionError::AccountSeedNotProvidedForNewAccount), - (false, Some(_)) => Err(TransactionError::AccountSeedProvidedForExistingAccount), + pub fn new( + account: Account, + account_seed: Option, + block_header: BlockHeader, + block_chain: ChainMmr, + input_notes: InputNotes, + ) -> Result { + match (account.is_new(), account_seed) { + (true, Some(seed)) => validate_account_seed(&account, seed) + .map_err(TransactionInputError::InvalidAccountSeed), + (true, None) => Err(TransactionInputError::AccountSeedNotProvidedForNewAccount), + (false, Some(_)) => Err(TransactionInputError::AccountSeedProvidedForExistingAccount), (false, None) => Ok(()), - } + }?; + + Ok(Self { + account, + account_seed, + block_header, + block_chain, + input_notes, + }) + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns account against which the transaction is to be executed. + pub fn account(&self) -> &Account { + &self.account + } + + /// For newly-created accounts, returns the account seed; for existing accounts, returns None. + pub fn account_seed(&self) -> Option { + self.account_seed + } + + /// Returns block header for the block referenced by the transaction. + pub fn block_header(&self) -> &BlockHeader { + &self.block_header + } + + /// Returns chain MMR containing authentication paths for all notes consumed by the + /// transaction. + pub fn block_chain(&self) -> &ChainMmr { + &self.block_chain + } + + /// Returns the notes to be consumed in the transaction. + pub fn input_notes(&self) -> &InputNotes { + &self.input_notes } } @@ -110,9 +155,9 @@ impl InputNotes { /// 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 { + pub fn new(notes: Vec) -> Result { if notes.len() > MAX_INPUT_NOTES_PER_TRANSACTION { - return Err(TransactionError::TooManyInputNotes { + return Err(TransactionInputError::TooManyInputNotes { max: MAX_INPUT_NOTES_PER_TRANSACTION, actual: notes.len(), }); @@ -121,7 +166,7 @@ impl InputNotes { let mut seen_notes = BTreeSet::new(); for note in notes.iter() { if !seen_notes.insert(note.nullifier().inner()) { - return Err(TransactionError::DuplicateInputNote(note.nullifier().inner())); + return Err(TransactionInputError::DuplicateInputNote(note.nullifier().inner())); } } diff --git a/objects/src/transaction/prepared_tx.rs b/objects/src/transaction/prepared_tx.rs index 4e637865a..a9457ca46 100644 --- a/objects/src/transaction/prepared_tx.rs +++ b/objects/src/transaction/prepared_tx.rs @@ -1,5 +1,4 @@ use super::{Account, BlockHeader, InputNotes, Program, TransactionInputs, TransactionScript}; -use crate::TransactionError; // PREPARED TRANSACTION // ================================================================================================ @@ -22,17 +21,12 @@ impl PreparedTransaction { // -------------------------------------------------------------------------------------------- /// Returns a new [PreparedTransaction] instantiated from the provided executable transaction /// program and inputs required to execute this program. - /// - /// # Returns an error if: - /// - For a new account, account seed is not provided or the provided seed is invalid. - /// - For an existing account, account seed was provided. pub fn new( program: Program, tx_script: Option, tx_inputs: TransactionInputs, - ) -> Result { - tx_inputs.validate_new_account_seed()?; - Ok(Self { program, tx_script, tx_inputs }) + ) -> Self { + Self { program, tx_script, tx_inputs } } // ACCESSORS @@ -45,17 +39,17 @@ impl PreparedTransaction { /// Returns the account for this transaction. pub fn account(&self) -> &Account { - &self.tx_inputs.account + self.tx_inputs.account() } /// Returns the block header for this transaction. pub fn block_header(&self) -> &BlockHeader { - &self.tx_inputs.block_header + self.tx_inputs.block_header() } /// Returns the notes to be consumed in this transaction. pub fn input_notes(&self) -> &InputNotes { - &self.tx_inputs.input_notes + self.tx_inputs.input_notes() } /// Return a reference the transaction script. diff --git a/objects/src/transaction/tx_script.rs b/objects/src/transaction/tx_script.rs index 497d2356e..91d267a4d 100644 --- a/objects/src/transaction/tx_script.rs +++ b/objects/src/transaction/tx_script.rs @@ -1,9 +1,9 @@ use super::{Digest, Felt, Word}; use crate::{ assembly::{Assembler, AssemblyContext, ProgramAst}, - errors::TransactionError, utils::collections::{BTreeMap, Vec}, vm::CodeBlock, + TransactionScriptError, }; // TRANSACTION SCRIPT @@ -39,10 +39,10 @@ impl TransactionScript { code: ProgramAst, inputs: T, assembler: &mut Assembler, - ) -> Result<(Self, CodeBlock), TransactionError> { + ) -> Result<(Self, CodeBlock), TransactionScriptError> { let code_block = assembler .compile_in_context(&code, &mut AssemblyContext::for_program(Some(&code))) - .map_err(TransactionError::ScriptCompilationError)?; + .map_err(TransactionScriptError::ScriptCompilationError)?; Ok(( Self { code, @@ -61,7 +61,7 @@ impl TransactionScript { code: ProgramAst, hash: Digest, inputs: T, - ) -> Result { + ) -> Result { Ok(Self { code, hash, diff --git a/objects/src/transaction/tx_witness.rs b/objects/src/transaction/tx_witness.rs index bc6d3ca88..8fe577a05 100644 --- a/objects/src/transaction/tx_witness.rs +++ b/objects/src/transaction/tx_witness.rs @@ -57,17 +57,17 @@ impl TransactionWitness { /// Returns the account state before the transaction was executed. pub fn account(&self) -> &Account { - &self.tx_inputs.account + self.tx_inputs.account() } /// Returns the notes consumed in this transaction. pub fn input_notes(&self) -> &InputNotes { - &self.tx_inputs.input_notes + self.tx_inputs.input_notes() } /// Returns the block header for the block against which the transaction was executed. pub fn block_header(&self) -> &BlockHeader { - &self.tx_inputs.block_header + self.tx_inputs.block_header() } /// Returns a reference to the transaction script. From 74f1930119073d34bb1847eec14a127bf50e0a7a Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 2 Jan 2024 01:38:32 -0800 Subject: [PATCH 21/21] feat: add more DataStoreError variants --- miden-tx/src/error.rs | 15 ++++++++++++--- miden-tx/src/executor/mod.rs | 8 ++++---- miden-tx/src/prover/mod.rs | 2 +- objects/src/transaction/inputs.rs | 2 ++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/miden-tx/src/error.rs b/miden-tx/src/error.rs index ed12e36b3..4a5016d1b 100644 --- a/miden-tx/src/error.rs +++ b/miden-tx/src/error.rs @@ -1,7 +1,8 @@ use core::fmt; use miden_objects::{ - assembly::AssemblyError, crypto::merkle::NodeIndex, NoteError, TransactionOutputError, + assembly::AssemblyError, crypto::merkle::NodeIndex, NoteError, TransactionInputError, + TransactionOutputError, }; use miden_verifier::VerificationError; @@ -9,6 +10,7 @@ use super::{AccountError, AccountId, Digest, ExecutionError}; // TRANSACTION COMPILER ERROR // ================================================================================================ + #[derive(Debug)] pub enum TransactionCompilerError { AccountInterfaceNotFound(AccountId), @@ -33,6 +35,7 @@ impl std::error::Error for TransactionCompilerError {} // TRANSACTION EXECUTOR ERROR // ================================================================================================ + #[derive(Debug)] pub enum TransactionExecutorError { CompileNoteScriptFailed(TransactionCompilerError), @@ -46,7 +49,7 @@ pub enum TransactionExecutorError { output_id: AccountId, }, LoadAccountFailed(TransactionCompilerError), - OutputConstructionFailed(TransactionOutputError), + InvalidTransactionOutput(TransactionOutputError), } impl fmt::Display for TransactionExecutorError { @@ -60,10 +63,11 @@ impl std::error::Error for TransactionExecutorError {} // TRANSACTION PROVER ERROR // ================================================================================================ + #[derive(Debug)] pub enum TransactionProverError { ProveTransactionProgramFailed(ExecutionError), - OutputConstructionFailed(TransactionOutputError), + InvalidTransactionOutput(TransactionOutputError), } impl fmt::Display for TransactionProverError { @@ -77,6 +81,7 @@ impl std::error::Error for TransactionProverError {} // TRANSACTION VERIFIER ERROR // ================================================================================================ + #[derive(Debug)] pub enum TransactionVerifierError { TransactionVerificationFailed(VerificationError), @@ -94,9 +99,13 @@ impl std::error::Error for TransactionVerifierError {} // DATA STORE ERROR // ================================================================================================ + #[derive(Debug)] pub enum DataStoreError { AccountNotFound(AccountId), + BlockNotFound(u32), + InvalidTransactionInput(TransactionInputError), + InternalError(String), NoteNotFound(u32, NodeIndex), } diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index 4aeb49213..0d89e5a34 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -23,7 +23,7 @@ use crate::host::EventHandler; /// /// Transaction execution consists of the following steps: /// - Fetch the data required to execute a transaction from the [DataStore]. -/// - Compile the transaction into a program using the [TransactionComplier](crate::TransactionCompiler). +/// - Compile the transaction into a program using the [TransactionCompiler](crate::TransactionCompiler). /// - Execute the transaction program and create an [ExecutedTransaction]. /// /// The transaction executor is generic over the [DataStore] which allows it to be used with @@ -170,7 +170,7 @@ impl TransactionExecutor { // -------------------------------------------------------------------------------------------- /// Fetches the data required to execute the transaction from the [DataStore], compiles the - /// transaction into an executable program using the [TransactionComplier], and returns a + /// transaction into an executable program using the [TransactionCompiler], and returns a /// [PreparedTransaction]. /// /// # Errors: @@ -219,7 +219,7 @@ fn build_executed_transaction( // parse transaction results let tx_outputs = TransactionKernel::parse_transaction_outputs(&stack_outputs, &map.into()) - .map_err(TransactionExecutorError::OutputConstructionFailed)?; + .map_err(TransactionExecutorError::InvalidTransactionOutput)?; let final_account = &tx_outputs.account; let initial_account = tx_inputs.account(); @@ -236,7 +236,7 @@ fn build_executed_transaction( // TODO: Fix delta extraction for new account creation // extract the account storage delta let storage_delta = extract_account_storage_delta(&store, initial_account, final_account) - .map_err(TransactionExecutorError::OutputConstructionFailed)?; + .map_err(TransactionExecutorError::InvalidTransactionOutput)?; // extract the nonce delta let nonce_delta = if initial_account.nonce() != final_account.nonce() { diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index f139f6c05..6967d05a7 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -60,7 +60,7 @@ impl TransactionProver { let (advice_provider, _event_handler) = host.into_parts(); let (_, map, _) = advice_provider.into_parts(); let tx_outputs = TransactionKernel::parse_transaction_outputs(&stack_outputs, &map.into()) - .map_err(TransactionProverError::OutputConstructionFailed)?; + .map_err(TransactionProverError::InvalidTransactionOutput)?; Ok(ProvenTransaction::new( account_id, diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index 0cde12ee4..b6265934b 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -49,6 +49,8 @@ impl TransactionInputs { (false, None) => Ok(()), }?; + // TODO: check if block_chain has authentication paths for all input notes + Ok(Self { account, account_seed,