diff --git a/CHANGELOG.md b/CHANGELOG.md index b31a56cffb..d5baebb56d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - Add `AccountTree` and `PartialAccountTree` wrappers and enforce ID prefix uniqueness (#1254). - Added a retry strategy for worker's health check (#1255). - Added pretty print for `AccountCode` (#1273). - +- [BREAKING] Refactored how foreign account inputs are passed to `TransactionExecutor`, and upgraded Rust version to 1.86 (#1229). ## 0.8.1 (2025-03-26) - `miden-objects` and `miden-tx` crates only. diff --git a/Cargo.lock b/Cargo.lock index c0b644b442..deed62196a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,11 +263,11 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288" dependencies = [ - "axum-core 0.5.0", + "axum-core 0.5.2", "bytes", "form_urlencoded", "futures-util", @@ -317,12 +317,12 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", @@ -2075,7 +2075,7 @@ name = "miden-proving-service" version = "0.9.0" dependencies = [ "async-trait", - "axum 0.8.1", + "axum 0.8.3", "bytes", "clap 4.5.32", "miden-block-prover", diff --git a/Cargo.toml b/Cargo.toml index 476dbfd235..0ad2435cc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ members = [ [workspace.package] edition = "2024" -rust-version = "1.85" +rust-version = "1.86" license = "MIT" authors = ["Miden contributors"] homepage = "https://polygon.technology/polygon-miden" diff --git a/README.md b/README.md index bc0fb0fb2e..890f04045a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/0xPolygonMiden/miden-base/blob/main/LICENSE) [![test](https://github.com/0xPolygonMiden/miden-base/actions/workflows/test.yml/badge.svg)](https://github.com/0xPolygonMiden/miden-base/actions/workflows/test.yml) [![build](https://github.com/0xPolygonMiden/miden-base/actions/workflows/build.yml/badge.svg)](https://github.com/0xPolygonMiden/miden-base/actions/workflows/build.yml) -[![RUST_VERSION](https://img.shields.io/badge/rustc-1.85+-lightgray.svg)](https://www.rust-lang.org/tools/install) +[![RUST_VERSION](https://img.shields.io/badge/rustc-1.86+-lightgray.svg)](https://www.rust-lang.org/tools/install) [![GitHub Release](https://img.shields.io/github/release/0xPolygonMiden/miden-base)](https://github.com/0xPolygonMiden/miden-base/releases/) Description and core structures for the Miden Rollup protocol. diff --git a/bin/bench-tx/src/main.rs b/bin/bench-tx/src/main.rs index fc53e0525e..dfc7d6729d 100644 --- a/bin/bench-tx/src/main.rs +++ b/bin/bench-tx/src/main.rs @@ -3,6 +3,7 @@ use std::{ fs::{File, read_to_string, write}, io::Write, path::Path, + sync::Arc, }; use miden_lib::{note::create_p2id_note, transaction::TransactionKernel}; @@ -67,17 +68,12 @@ pub fn benchmark_default_tx() -> Result { let account_id = tx_context.account().id(); let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); - + let tx_args = tx_context.tx_args().clone(); + let notes = tx_context.tx_inputs().input_notes().clone(); let executor: TransactionExecutor = - TransactionExecutor::new(tx_context.get_data_store(), None).with_tracing(); + TransactionExecutor::new(Arc::new(tx_context), None).with_tracing(); let executed_transaction = executor - .execute_transaction(account_id, block_ref, ¬e_ids, tx_context.tx_args().clone()) + .execute_transaction(account_id, block_ref, notes, tx_args) .map_err(|e| e.to_string())?; Ok(executed_transaction.into()) @@ -116,26 +112,21 @@ pub fn benchmark_p2id() -> Result { let tx_context = TransactionContextBuilder::new(target_account.clone()) .input_notes(vec![note.clone()]) .build(); + let block_ref = tx_context.tx_inputs().block_header().block_num(); - let executor = TransactionExecutor::new(tx_context.get_data_store(), Some(falcon_auth.clone())) - .with_tracing(); + let notes = tx_context.tx_inputs().input_notes().clone(); - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); + let executor = + TransactionExecutor::new(Arc::new(tx_context), Some(falcon_auth.clone())).with_tracing(); let tx_script_target = TransactionScript::compile(DEFAULT_AUTH_SCRIPT, [], TransactionKernel::assembler()) .unwrap(); - let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); + let tx_args_target = TransactionArgs::default().with_tx_script(tx_script_target); // execute transaction let executed_transaction = executor - .execute_transaction(target_account.id(), block_ref, ¬e_ids, tx_args_target) + .execute_transaction(target_account.id(), block_ref, notes, tx_args_target) .unwrap(); Ok(executed_transaction.into()) diff --git a/crates/miden-lib/src/transaction/inputs.rs b/crates/miden-lib/src/transaction/inputs.rs index ceb0011aca..d5395e5671 100644 --- a/crates/miden-lib/src/transaction/inputs.rs +++ b/crates/miden-lib/src/transaction/inputs.rs @@ -1,7 +1,7 @@ use alloc::vec::Vec; use miden_objects::{ - Digest, EMPTY_WORD, Felt, FieldElement, WORD_SIZE, Word, ZERO, + Digest, EMPTY_WORD, Felt, FieldElement, TransactionInputError, WORD_SIZE, Word, ZERO, account::{Account, StorageSlot}, transaction::{ChainMmr, InputNote, TransactionArgs, TransactionInputs, TransactionScript}, vm::AdviceInputs, @@ -22,7 +22,7 @@ pub(super) fn extend_advice_inputs( tx_inputs: &TransactionInputs, tx_args: &TransactionArgs, advice_inputs: &mut AdviceInputs, -) { +) -> Result<(), TransactionInputError> { // TODO: remove this value and use a user input instead let kernel_version = 0; @@ -32,8 +32,13 @@ pub(super) fn extend_advice_inputs( add_kernel_commitments_to_advice_inputs(advice_inputs, kernel_version); 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, tx_args, advice_inputs); + add_input_notes_to_advice_inputs(tx_inputs, tx_args, advice_inputs)?; + for foreign_account in tx_args.foreign_accounts() { + TransactionKernel::extend_advice_inputs_for_account(advice_inputs, foreign_account)?; + } + advice_inputs.extend(tx_args.advice_inputs().clone()); + Ok(()) } // ADVICE STACK BUILDER @@ -223,10 +228,10 @@ fn add_input_notes_to_advice_inputs( tx_inputs: &TransactionInputs, tx_args: &TransactionArgs, inputs: &mut AdviceInputs, -) { +) -> Result<(), TransactionInputError> { // if there are no input notes, nothing is added to the advice inputs if tx_inputs.input_notes().is_empty() { - return; + return Ok(()); } let mut note_data = Vec::new(); @@ -283,7 +288,12 @@ fn add_input_notes_to_advice_inputs( proof.location().node_index_in_block().into(), note.commitment(), ) - .unwrap(), + .map_err(|err| { + TransactionInputError::InvalidMerklePath( + format!("input note ID {}", note.id()).into(), + err, + ) + })?, ); note_data.push(proof.location().block_num().into()); note_data.extend(note_block_header.sub_commitment()); @@ -300,6 +310,7 @@ fn add_input_notes_to_advice_inputs( // NOTE: keep map in sync with the `prologue::process_input_notes_data` kernel procedure inputs.extend_map([(tx_inputs.input_notes().commitment(), note_data)]); + Ok(()) } // KERNEL COMMITMENTS INJECTOR diff --git a/crates/miden-lib/src/transaction/mod.rs b/crates/miden-lib/src/transaction/mod.rs index e877784f0f..f6eefaaa47 100644 --- a/crates/miden-lib/src/transaction/mod.rs +++ b/crates/miden-lib/src/transaction/mod.rs @@ -1,13 +1,13 @@ use alloc::{string::ToString, sync::Arc, vec::Vec}; use miden_objects::{ - Digest, EMPTY_WORD, Felt, TransactionOutputError, ZERO, - account::{AccountCode, AccountHeader, AccountId, AccountStorageHeader}, + Digest, EMPTY_WORD, Felt, TransactionInputError, TransactionOutputError, ZERO, + account::{AccountCode, AccountId}, assembly::{Assembler, DefaultSourceManager, KernelLibrary}, - block::{AccountWitness, BlockNumber}, - crypto::merkle::MerkleError, + block::BlockNumber, transaction::{ - OutputNote, OutputNotes, TransactionArgs, TransactionInputs, TransactionOutputs, + ForeignAccountInputs, OutputNote, OutputNotes, TransactionArgs, TransactionInputs, + TransactionOutputs, }, utils::{serde::Deserializable, sync::LazyLock}, vm::{AdviceInputs, AdviceMap, Program, ProgramInfo, StackInputs, StackOutputs}, @@ -114,7 +114,7 @@ impl TransactionKernel { tx_inputs: &TransactionInputs, tx_args: &TransactionArgs, init_advice_inputs: Option, - ) -> (StackInputs, AdviceInputs) { + ) -> Result<(StackInputs, AdviceInputs), TransactionInputError> { let account = tx_inputs.account(); let stack_inputs = TransactionKernel::build_input_stack( @@ -126,9 +126,9 @@ impl TransactionKernel { ); let mut advice_inputs = init_advice_inputs.unwrap_or_default(); - inputs::extend_advice_inputs(tx_inputs, tx_args, &mut advice_inputs); + inputs::extend_advice_inputs(tx_inputs, tx_args, &mut advice_inputs)?; - (stack_inputs, advice_inputs) + Ok((stack_inputs, advice_inputs)) } // ASSEMBLER CONSTRUCTOR @@ -189,36 +189,30 @@ impl TransactionKernel { .expect("Invalid stack input") } - /// Extends the advice inputs with account data and Merkle proofs. - /// - /// Where: - /// - account_header is the header of the account which data will be used for the extension. - /// - account_code is the code of the account which will be used for the extension. - /// - storage_header is the header of the storage which data will be used for the extension. - /// - merkle_path is the authentication path from the account root of the block header to the - /// account. + /// Extends the advice inputs with the account-specific inputs. pub fn extend_advice_inputs_for_account( advice_inputs: &mut AdviceInputs, - account_header: &AccountHeader, - account_code: &AccountCode, - storage_header: &AccountStorageHeader, - account_witness: &AccountWitness, - ) -> Result<(), MerkleError> { + foreign_account_inputs: &ForeignAccountInputs, + ) -> Result<(), TransactionInputError> { + let account_header = foreign_account_inputs.account_header(); + let storage_header = foreign_account_inputs.storage_header(); + let account_code = foreign_account_inputs.account_code(); + let account_witness = foreign_account_inputs.witness(); + let storage_proofs = foreign_account_inputs.storage_map_proofs(); let account_id = account_header.id(); - let storage_root = account_header.storage_commitment(); - let code_root = account_header.code_commitment(); + // Note: keep in sync with the start_foreign_context kernel procedure - let account_key = + let account_key: Digest = Digest::from([account_id.suffix(), account_id.prefix().as_felt(), ZERO, ZERO]); // Extend the advice inputs with the new data advice_inputs.extend_map([ - // ACCOUNT_ID -> [ID_AND_NONCE, VAULT_ROOT, STORAGE_ROOT, CODE_ROOT] + // ACCOUNT_ID -> [ID_AND_NONCE, VAULT_ROOT, STORAGE_COMMITMENT, CODE_COMMITMENT] (account_key, account_header.as_elements()), - // STORAGE_ROOT -> [STORAGE_SLOT_DATA] - (storage_root, storage_header.as_elements()), - // CODE_ROOT -> [ACCOUNT_CODE_DATA] - (code_root, account_code.as_elements()), + // STORAGE_COMMITMENT -> [STORAGE_SLOT_DATA] + (account_header.storage_commitment(), storage_header.as_elements()), + // CODE_COMMITMENT -> [ACCOUNT_CODE_DATA] + (account_header.code_commitment(), account_code.as_elements()), ]); let account_leaf = account_witness.leaf(); @@ -228,11 +222,37 @@ impl TransactionKernel { advice_inputs.extend_merkle_store( account_witness .path() - .inner_nodes(account_id.prefix().as_u64(), account_leaf_hash)?, + .inner_nodes(account_id.prefix().as_u64(), account_leaf_hash) + .map_err(|err| { + TransactionInputError::InvalidMerklePath( + format!("foreign account ID {}", account_id).into(), + err, + ) + })?, ); + // populate advice map with the account's leaf advice_inputs.extend_map([(account_leaf_hash, account_leaf.to_elements())]); + // Load merkle nodes for storage maps + for proof in storage_proofs { + // Extend the merkle store and map with the storage maps + advice_inputs.extend_merkle_store( + proof + .path() + .inner_nodes(proof.leaf().index().value(), proof.leaf().hash()) + .map_err(|err| { + TransactionInputError::InvalidMerklePath( + format!("foreign account ID {} storage proof", account_id).into(), + err, + ) + })?, + ); + // Populate advice map with Sparse Merkle Tree leaf nodes + advice_inputs + .extend_map(core::iter::once((proof.leaf().hash(), proof.leaf().to_elements()))); + } + Ok(()) } diff --git a/crates/miden-objects/src/account/storage/header.rs b/crates/miden-objects/src/account/storage/header.rs index a0423ae1e3..515084e096 100644 --- a/crates/miden-objects/src/account/storage/header.rs +++ b/crates/miden-objects/src/account/storage/header.rs @@ -1,6 +1,8 @@ use alloc::vec::Vec; -use super::{AccountStorage, Felt, StorageSlot, StorageSlotType, Word}; +use vm_processor::Digest; + +use super::{AccountStorage, Felt, Hasher, StorageSlot, StorageSlotType, Word}; use crate::{ AccountError, ZERO, utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, @@ -90,6 +92,12 @@ impl AccountStorageHeader { }) } + // NOTE: The way of computing the commitment should be kept in sync with `AccountStorage` + /// Computes the account storage header commitment. + pub fn compute_commitment(&self) -> Digest { + Hasher::hash_elements(&self.as_elements()) + } + /// Converts storage slots of this account storage header into a vector of field elements. /// /// This is done by first converting each storage slot into exactly 8 elements as follows: diff --git a/crates/miden-objects/src/account/storage/mod.rs b/crates/miden-objects/src/account/storage/mod.rs index aa72c237de..de3e0e76bb 100644 --- a/crates/miden-objects/src/account/storage/mod.rs +++ b/crates/miden-objects/src/account/storage/mod.rs @@ -271,7 +271,7 @@ fn slots_as_elements(slots: &[StorageSlot]) -> Vec { } /// Computes the commitment to the given slots -fn build_slots_commitment(slots: &[StorageSlot]) -> Digest { +pub fn build_slots_commitment(slots: &[StorageSlot]) -> Digest { let elements = slots_as_elements(slots); Hasher::hash_elements(&elements) } diff --git a/crates/miden-objects/src/batch/account_update.rs b/crates/miden-objects/src/batch/account_update.rs index 99fe8a9abb..414d802694 100644 --- a/crates/miden-objects/src/batch/account_update.rs +++ b/crates/miden-objects/src/batch/account_update.rs @@ -1,3 +1,5 @@ +use alloc::boxed::Box; + use crate::{ Digest, account::{AccountId, delta::AccountUpdateDetails}, @@ -106,7 +108,9 @@ impl BatchAccountUpdate { } self.details = self.details.clone().merge(tx.account_update().details().clone()).map_err( - |source_err| BatchAccountUpdateError::TransactionUpdateMergeError(tx.id(), source_err), + |source_err| { + BatchAccountUpdateError::TransactionUpdateMergeError(tx.id(), Box::new(source_err)) + }, )?; self.final_state_commitment = tx.account_update().final_state_commitment(); diff --git a/crates/miden-objects/src/block/proposed_block.rs b/crates/miden-objects/src/block/proposed_block.rs index a4d034e29f..1eb68461e5 100644 --- a/crates/miden-objects/src/block/proposed_block.rs +++ b/crates/miden-objects/src/block/proposed_block.rs @@ -1,4 +1,5 @@ use alloc::{ + boxed::Box, collections::{BTreeMap, BTreeSet}, vec::Vec, }; @@ -681,7 +682,7 @@ impl AccountUpdateAggregator { details = Some(match details { None => update_details, Some(details) => details.merge(update_details).map_err(|source| { - ProposedBlockError::AccountUpdateError { account_id, source } + ProposedBlockError::AccountUpdateError { account_id, source: Box::new(source) } })?, }); } diff --git a/crates/miden-objects/src/errors.rs b/crates/miden-objects/src/errors.rs index 4a52ac155c..1d8bc833c5 100644 --- a/crates/miden-objects/src/errors.rs +++ b/crates/miden-objects/src/errors.rs @@ -282,7 +282,7 @@ pub enum BatchAccountUpdateError { )] AccountUpdateInitialStateMismatch(TransactionId), #[error("failed to merge account delta from transaction {0}")] - TransactionUpdateMergeError(TransactionId, #[source] AccountDeltaError), + TransactionUpdateMergeError(TransactionId, #[source] Box), } // ASSET ERROR @@ -475,6 +475,8 @@ pub enum TransactionInputError { InputNoteNotInBlock(NoteId, BlockNumber), #[error("account ID computed from seed is invalid")] InvalidAccountIdSeed(#[source] AccountIdError), + #[error("merkle path for {0} is invalid")] + InvalidMerklePath(Box, #[source] MerkleError), #[error( "total number of input notes is {0} which exceeds the maximum of {MAX_INPUT_NOTES_PER_TX}" )] @@ -804,7 +806,7 @@ pub enum ProposedBlockError { #[error("failed to merge transaction delta into account {account_id}")] AccountUpdateError { account_id: AccountId, - source: AccountDeltaError, + source: Box, }, } diff --git a/crates/miden-objects/src/note/note_tag.rs b/crates/miden-objects/src/note/note_tag.rs index 72dacc5706..4deaa08b05 100644 --- a/crates/miden-objects/src/note/note_tag.rs +++ b/crates/miden-objects/src/note/note_tag.rs @@ -325,7 +325,7 @@ mod tests { ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, - ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_2, ACCOUNT_ID_SENDER, + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2, ACCOUNT_ID_SENDER, }, }; @@ -342,7 +342,8 @@ mod tests { AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(), AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2).unwrap(), AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE).unwrap(), - AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_2).unwrap(), + AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2) + .unwrap(), AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(), AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap(), AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2).unwrap(), diff --git a/crates/miden-objects/src/testing/account_id.rs b/crates/miden-objects/src/testing/account_id.rs index 06f45286b1..e8e154c9a8 100644 --- a/crates/miden-objects/src/testing/account_id.rs +++ b/crates/miden-objects/src/testing/account_id.rs @@ -37,7 +37,7 @@ pub const ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE: u128 = account_id( AccountStorageMode::Public, 0xacdd_eefc, ); -pub const ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_2: u128 = account_id( +pub const ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2: u128 = account_id( AccountType::RegularAccountUpdatableCode, AccountStorageMode::Public, 0xeeff_ccdd, diff --git a/crates/miden-objects/src/transaction/executed_tx.rs b/crates/miden-objects/src/transaction/executed_tx.rs index d7b2e74191..e441787795 100644 --- a/crates/miden-objects/src/transaction/executed_tx.rs +++ b/crates/miden-objects/src/transaction/executed_tx.rs @@ -7,7 +7,6 @@ use super::{ TransactionOutputs, TransactionWitness, }; use crate::{ - account::AccountCode, block::BlockNumber, utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, }; @@ -30,7 +29,6 @@ pub struct ExecutedTransaction { id: OnceCell, tx_inputs: TransactionInputs, tx_outputs: TransactionOutputs, - account_codes: Vec, account_delta: AccountDelta, tx_args: TransactionArgs, advice_witness: AdviceInputs, @@ -48,7 +46,6 @@ impl ExecutedTransaction { pub fn new( tx_inputs: TransactionInputs, tx_outputs: TransactionOutputs, - account_codes: Vec, account_delta: AccountDelta, tx_args: TransactionArgs, advice_witness: AdviceInputs, @@ -61,7 +58,6 @@ impl ExecutedTransaction { id: OnceCell::new(), tx_inputs, tx_outputs, - account_codes, account_delta, tx_args, advice_witness, @@ -150,7 +146,6 @@ impl ExecutedTransaction { tx_inputs: self.tx_inputs, tx_args: self.tx_args, advice_witness: self.advice_witness, - account_codes: self.account_codes, }; (self.account_delta, self.tx_outputs, tx_witness, self.tx_measurements) } @@ -174,7 +169,6 @@ impl Serializable for ExecutedTransaction { fn write_into(&self, target: &mut W) { self.tx_inputs.write_into(target); self.tx_outputs.write_into(target); - self.account_codes.write_into(target); self.account_delta.write_into(target); self.tx_args.write_into(target); self.advice_witness.write_into(target); @@ -186,7 +180,6 @@ impl Deserializable for ExecutedTransaction { fn read_from(source: &mut R) -> Result { let tx_inputs = TransactionInputs::read_from(source)?; let tx_outputs = TransactionOutputs::read_from(source)?; - let account_codes = Vec::::read_from(source)?; let account_delta = AccountDelta::read_from(source)?; let tx_args = TransactionArgs::read_from(source)?; let advice_witness = AdviceInputs::read_from(source)?; @@ -195,7 +188,6 @@ impl Deserializable for ExecutedTransaction { Ok(Self::new( tx_inputs, tx_outputs, - account_codes, account_delta, tx_args, advice_witness, diff --git a/crates/miden-objects/src/transaction/inputs.rs b/crates/miden-objects/src/transaction/inputs.rs index d29d15d38d..b331bd80d4 100644 --- a/crates/miden-objects/src/transaction/inputs.rs +++ b/crates/miden-objects/src/transaction/inputs.rs @@ -1,11 +1,15 @@ use alloc::{collections::BTreeSet, vec::Vec}; use core::fmt::Debug; +use miden_crypto::merkle::{SmtProof, SmtProofError}; + use super::{BlockHeader, ChainMmr, Digest, Felt, Hasher, Word}; use crate::{ MAX_INPUT_NOTES_PER_TX, TransactionInputError, - account::{Account, AccountId, AccountIdAnchor}, - block::BlockNumber, + account::{ + Account, AccountCode, AccountHeader, AccountId, AccountIdAnchor, AccountStorageHeader, + }, + block::{AccountWitness, BlockNumber}, note::{Note, NoteId, NoteInclusionProof, NoteLocation, Nullifier}, utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, }; @@ -302,7 +306,7 @@ impl Default for InputNotes { } // SERIALIZATION -// ------------------------------------------------------------------------------------------------ +// ================================================================================================ impl Serializable for InputNotes { fn write_into(&self, target: &mut W) { @@ -322,7 +326,7 @@ impl Deserializable for InputNotes(notes: &[T]) -> Digest { // Note: This implementation must be kept in sync with the kernel's `process_input_notes_data` @@ -531,3 +535,191 @@ pub fn validate_account_seed( (false, None) => Ok(()), } } + +// FOREIGN ACCOUNT INPUTS +// ================================================================================================ + +/// Contains information about a foreign account, with everything required to execute its code. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ForeignAccountInputs { + /// Account header of the foreign account. + account_header: AccountHeader, + /// Header information about the account's storage. + storage_header: AccountStorageHeader, + /// Code associated with the account. + account_code: AccountCode, + /// Proof of the account's inclusion in the account tree for this account's state commitment. + witness: AccountWitness, + /// Storage SMT proof for storage map values that the transaction will access. + storage_map_proofs: Vec, +} + +impl ForeignAccountInputs { + /// Creates a new [`ForeignAccountInputs`] + pub fn new( + account_header: AccountHeader, + storage_header: AccountStorageHeader, + account_code: AccountCode, + witness: AccountWitness, + storage_map_proofs: Vec, + ) -> ForeignAccountInputs { + ForeignAccountInputs { + account_header, + storage_header, + account_code, + witness, + storage_map_proofs, + } + } + + /// Returns the account's [`AccountId`] + pub fn id(&self) -> AccountId { + self.account_header.id() + } + + /// Returns the account's [`AccountHeader`] + pub fn account_header(&self) -> &AccountHeader { + &self.account_header + } + + /// Returns the account's [`AccountStorageHeader`]. + pub fn storage_header(&self) -> &AccountStorageHeader { + &self.storage_header + } + + /// Returns the account's storage maps. + pub fn storage_map_proofs(&self) -> &[SmtProof] { + &self.storage_map_proofs + } + + /// Returns the account's [`AccountCode`]. + pub fn account_code(&self) -> &AccountCode { + &self.account_code + } + + /// Returns the account witness. + pub fn witness(&self) -> &AccountWitness { + &self.witness + } + + /// Computes account root based on the account witness. + pub fn compute_account_root(&self) -> Result { + let smt_merkle_path = self.witness.path().clone(); + let smt_leaf = self.witness.leaf(); + let root = SmtProof::new(smt_merkle_path, smt_leaf)?.compute_root(); + + Ok(root) + } + + /// Returns `true` if the [`ForeignAccountInputs`]'s inner fields are cohesive. + pub fn is_valid(&self) -> bool { + self.account_header.code_commitment() == self.account_code.commitment() + && self.account_header.storage_commitment() == self.storage_header.compute_commitment() + && self.account_header.commitment() == self.witness.state_commitment() + } + + /// Extends the storage proofs with the input `smt_proofs` and returns the new structure + #[must_use] + pub fn with_storage_map_proofs( + mut self, + smt_proofs: impl IntoIterator, + ) -> Self { + self.storage_map_proofs.extend(smt_proofs); + self + } + + /// Consumes the [`ForeignAccountInputs`] and returns its parts. + pub fn into_parts( + self, + ) -> (AccountHeader, AccountStorageHeader, AccountCode, AccountWitness, Vec) { + ( + self.account_header, + self.storage_header, + self.account_code, + self.witness, + self.storage_map_proofs, + ) + } +} + +impl Serializable for ForeignAccountInputs { + fn write_into(&self, target: &mut W) { + self.account_header.write_into(target); + self.storage_header.write_into(target); + self.account_code.write_into(target); + self.witness.write_into(target); + self.storage_map_proofs.write_into(target); + } +} + +impl Deserializable for ForeignAccountInputs { + fn read_from( + source: &mut R, + ) -> Result { + let account_header = AccountHeader::read_from(source)?; + let storage_header = AccountStorageHeader::read_from(source)?; + let account_code = AccountCode::read_from(source)?; + let account_witness = AccountWitness::read_from(source)?; + let storage_maps = Vec::::read_from(source)?; + Ok(ForeignAccountInputs::new( + account_header, + storage_header, + account_code, + account_witness, + storage_maps, + )) + } +} + +#[cfg(test)] +mod tests { + use std::vec::Vec; + + use miden_crypto::merkle::MerklePath; + use vm_core::{ + Felt, + utils::{Deserializable, Serializable}, + }; + use vm_processor::SMT_DEPTH; + + use super::ForeignAccountInputs; + use crate::{ + account::{Account, AccountCode, AccountHeader, AccountId, AccountStorage}, + asset::AssetVault, + block::AccountWitness, + testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, + }; + + #[test] + fn serde_roundtrip() { + let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(); + let code = AccountCode::mock(); + let vault = AssetVault::new(&[]).unwrap(); + let storage = AccountStorage::new(vec![]).unwrap(); + let account = Account::from_parts(id, vault, storage, code, Felt::new(10)); + + let commitment = account.commitment(); + + let mut merkle_nodes = Vec::with_capacity(SMT_DEPTH as usize); + for _ in 0..(SMT_DEPTH as usize) { + merkle_nodes.push(commitment); + } + let merkle_path = MerklePath::new(merkle_nodes); + + let header: AccountHeader = account.clone().into(); + let (_, _, storage, code, _) = account.into_parts(); + + let fpi_inputs = ForeignAccountInputs::new( + header, + storage.get_header(), + code, + AccountWitness::new(id, commitment, merkle_path).unwrap(), + vec![], + ); + + let serialized = fpi_inputs.to_bytes(); + let deserialized = ForeignAccountInputs::read_from_bytes(&serialized).unwrap(); + assert_eq!(deserialized, fpi_inputs); + assert!(deserialized.is_valid()); + } +} diff --git a/crates/miden-objects/src/transaction/mod.rs b/crates/miden-objects/src/transaction/mod.rs index 6a62e0ce11..fe9b51be61 100644 --- a/crates/miden-objects/src/transaction/mod.rs +++ b/crates/miden-objects/src/transaction/mod.rs @@ -19,7 +19,9 @@ mod tx_witness; pub use chain_mmr::ChainMmr; pub use executed_tx::{ExecutedTransaction, TransactionMeasurements}; -pub use inputs::{InputNote, InputNotes, ToInputNoteCommitments, TransactionInputs}; +pub use inputs::{ + ForeignAccountInputs, InputNote, InputNotes, ToInputNoteCommitments, TransactionInputs, +}; pub use ordered_transactions::OrderedTransactionHeaders; pub use outputs::{OutputNote, OutputNotes, TransactionOutputs}; pub use proven_tx::{ diff --git a/crates/miden-objects/src/transaction/tx_args.rs b/crates/miden-objects/src/transaction/tx_args.rs index dcaa3a7386..675f03cea4 100644 --- a/crates/miden-objects/src/transaction/tx_args.rs +++ b/crates/miden-objects/src/transaction/tx_args.rs @@ -1,9 +1,13 @@ -use alloc::{collections::BTreeMap, sync::Arc, vec::Vec}; +use alloc::{ + collections::{BTreeMap, BTreeSet}, + sync::Arc, + vec::Vec, +}; use assembly::{Assembler, Compile}; use miden_crypto::merkle::InnerNodeInfo; -use super::{Digest, Felt, Word}; +use super::{Digest, Felt, ForeignAccountInputs, Word}; use crate::{ MastForest, MastNodeId, TransactionScriptError, note::{NoteId, NoteRecipient}, @@ -27,6 +31,7 @@ pub struct TransactionArgs { tx_script: Option, note_args: BTreeMap, advice_inputs: AdviceInputs, + foreign_account_inputs: Vec, } impl TransactionArgs { @@ -42,6 +47,7 @@ impl TransactionArgs { tx_script: Option, note_args: Option>, advice_map: AdviceMap, + foreign_account_inputs: Vec, ) -> Self { let mut advice_inputs = AdviceInputs::default().with_map(advice_map); // add transaction script inputs to the advice inputs' map @@ -54,23 +60,31 @@ impl TransactionArgs { tx_script, note_args: note_args.unwrap_or_default(), advice_inputs, + foreign_account_inputs, } } /// Returns new [TransactionArgs] instantiated with the provided transaction script. - pub fn with_tx_script(tx_script: TransactionScript) -> Self { - Self::new(Some(tx_script), Some(BTreeMap::default()), AdviceMap::default()) + #[must_use] + pub fn with_tx_script(mut self, tx_script: TransactionScript) -> Self { + self.tx_script = Some(tx_script); + self } /// Returns new [TransactionArgs] instantiated with the provided note arguments. - pub fn with_note_args(note_args: BTreeMap) -> Self { - Self::new(None, Some(note_args), AdviceMap::default()) + #[must_use] + pub fn with_note_args(mut self, note_args: BTreeMap) -> Self { + self.note_args = note_args; + self } - /// Returns the provided [TransactionArgs] with advice inputs extended with the passed-in - /// `advice_inputs`. - pub fn with_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self { - self.advice_inputs.extend(advice_inputs); + /// Returns the modified [TransactionArgs] with the provided foreign account inputs. + #[must_use] + pub fn with_foreign_account_inputs( + mut self, + foreign_account_inputs: Vec, + ) -> Self { + self.foreign_account_inputs = foreign_account_inputs; self } @@ -92,6 +106,19 @@ impl TransactionArgs { &self.advice_inputs } + /// Returns a reference to the foreign account inputs in the transaction args. + pub fn foreign_accounts(&self) -> &[ForeignAccountInputs] { + &self.foreign_account_inputs + } + + /// Collects and returns a set containing all code commitments from foreign accounts. + pub fn foreign_account_code_commitments(&self) -> BTreeSet { + self.foreign_accounts() + .iter() + .map(|acc| acc.account_code().commitment()) + .collect() + } + // STATE MUTATORS // -------------------------------------------------------------------------------------------- @@ -134,6 +161,11 @@ impl TransactionArgs { } } + /// Extends the advice inputs in self with the provided ones. + pub fn extend_advice_inputs(&mut self, advice_inputs: AdviceInputs) { + self.advice_inputs.extend(advice_inputs); + } + /// Extends the internal advice inputs' map with the provided key-value pairs. pub fn extend_advice_map)>>(&mut self, iter: T) { self.advice_inputs.extend_map(iter) @@ -150,6 +182,7 @@ impl Serializable for TransactionArgs { self.tx_script.write_into(target); self.note_args.write_into(target); self.advice_inputs.write_into(target); + self.foreign_account_inputs.write_into(target); } } @@ -158,8 +191,14 @@ impl Deserializable for TransactionArgs { let tx_script = Option::::read_from(source)?; let note_args = BTreeMap::::read_from(source)?; let advice_inputs = AdviceInputs::read_from(source)?; + let foreign_accounts = Vec::::read_from(source)?; - Ok(Self { tx_script, note_args, advice_inputs }) + Ok(Self { + tx_script, + note_args, + advice_inputs, + foreign_account_inputs: foreign_accounts, + }) } } @@ -275,7 +314,7 @@ mod tests { #[test] fn test_tx_args_serialization() { - let args = TransactionArgs::new(None, None, AdviceMap::default()); + let args = TransactionArgs::new(None, None, AdviceMap::default(), std::vec::Vec::default()); let bytes: std::vec::Vec = args.to_bytes(); let decoded = TransactionArgs::read_from_bytes(&bytes).unwrap(); diff --git a/crates/miden-objects/src/transaction/tx_witness.rs b/crates/miden-objects/src/transaction/tx_witness.rs index 23c7ff1456..7fd67c8eb3 100644 --- a/crates/miden-objects/src/transaction/tx_witness.rs +++ b/crates/miden-objects/src/transaction/tx_witness.rs @@ -1,10 +1,5 @@ -use alloc::vec::Vec; - use super::{AdviceInputs, TransactionArgs, TransactionInputs}; -use crate::{ - account::AccountCode, - utils::serde::{ByteReader, Deserializable, DeserializationError, Serializable}, -}; +use crate::utils::serde::{ByteReader, Deserializable, DeserializationError, Serializable}; // TRANSACTION WITNESS // ================================================================================================ @@ -32,7 +27,6 @@ pub struct TransactionWitness { pub tx_inputs: TransactionInputs, pub tx_args: TransactionArgs, pub advice_witness: AdviceInputs, - pub account_codes: Vec, } // SERIALIZATION @@ -43,7 +37,6 @@ impl Serializable for TransactionWitness { self.tx_inputs.write_into(target); self.tx_args.write_into(target); self.advice_witness.write_into(target); - self.account_codes.write_into(target); } } @@ -52,12 +45,6 @@ impl Deserializable for TransactionWitness { let tx_inputs = TransactionInputs::read_from(source)?; let tx_args = TransactionArgs::read_from(source)?; let advice_witness = AdviceInputs::read_from(source)?; - let account_codes = >::read_from(source)?; - Ok(Self { - tx_inputs, - tx_args, - advice_witness, - account_codes, - }) + Ok(Self { tx_inputs, tx_args, advice_witness }) } } diff --git a/crates/miden-tx/README.md b/crates/miden-tx/README.md index 42c585a750..5681863349 100644 --- a/crates/miden-tx/README.md +++ b/crates/miden-tx/README.md @@ -16,7 +16,7 @@ Once a store is available, a `TransactionExecutor` object can be used to execute ```rust let executor = TransactionExecutor::new(store); -let executed_transaction = executor.execute_transaction(account_id, block_ref, note_ids, tx_args); +let executed_transaction = executor.execute_transaction(account_id, block_ref, notes, tx_args); ``` With the transaction execution done, it is then possible to create a proof: diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index 3ffc6b2846..d3a8cdc425 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -3,7 +3,7 @@ use core::error::Error; use miden_objects::{ AccountError, Felt, ProvenTransactionError, TransactionInputError, TransactionOutputError, - account::AccountId, block::BlockNumber, note::NoteId, + account::AccountId, block::BlockNumber, crypto::merkle::SmtProofError, note::NoteId, }; use miden_verifier::VerificationError; use thiserror::Error; @@ -14,10 +14,12 @@ use vm_processor::ExecutionError; #[derive(Debug, Error)] pub enum TransactionExecutorError { - #[error("failed to execute transaction kernel program")] - TransactionProgramExecutionFailed(#[source] ExecutionError), #[error("failed to fetch transaction inputs from the data store")] FetchTransactionInputsFailed(#[source] DataStoreError), + #[error("foreign account inputs for ID {0} are not anchored on reference block")] + ForeignAccountNotAnchoredInReference(AccountId), + #[error("failed to create transaction inputs")] + InvalidTransactionInputs(#[source] TransactionInputError), #[error("input account ID {input_id} does not match output account ID {output_id}")] InconsistentAccountId { input_id: AccountId, @@ -28,10 +30,18 @@ pub enum TransactionExecutorError { expected: Option, actual: Option, }, - #[error("failed to construct transaction outputs")] - TransactionOutputConstructionFailed(#[source] TransactionOutputError), + #[error("account witness provided for account ID {0} is invalid")] + InvalidAccountWitness(AccountId, #[source] SmtProofError), + #[error( + "input note {0} was created in a block past the transaction reference block number ({1})" + )] + NoteBlockPastReferenceBlock(NoteId, BlockNumber), #[error("failed to create transaction host")] TransactionHostCreationFailed(#[source] TransactionHostError), + #[error("failed to construct transaction outputs")] + TransactionOutputConstructionFailed(#[source] TransactionOutputError), + #[error("failed to execute transaction kernel program")] + TransactionProgramExecutionFailed(#[source] ExecutionError), } // TRANSACTION PROVER ERROR @@ -41,6 +51,8 @@ pub enum TransactionExecutorError { pub enum TransactionProverError { #[error("failed to apply account delta")] AccountDeltaApplyFailed(#[source] AccountError), + #[error("transaction inputs are not valid")] + InvalidTransactionInputs(#[source] TransactionInputError), #[error("failed to construct transaction outputs")] TransactionOutputConstructionFailed(#[source] TransactionOutputError), #[error("failed to build proven transaction")] @@ -111,12 +123,6 @@ pub enum DataStoreError { AccountNotFound(AccountId), #[error("block with number {0} not found in data store")] BlockNotFound(BlockNumber), - #[error("failed to create transaction inputs")] - InvalidTransactionInput(#[source] TransactionInputError), - #[error("note with id {0} is already consumed")] - NoteAlreadyConsumed(NoteId), - #[error("not with id {0} not found in data store")] - NoteNotFound(NoteId), /// Custom error variant for implementors of the [`DataStore`](crate::executor::DataStore) /// trait. #[error("{error_msg}")] diff --git a/crates/miden-tx/src/executor/data_store.rs b/crates/miden-tx/src/executor/data_store.rs index 6d520aaa4b..f31dee7f59 100644 --- a/crates/miden-tx/src/executor/data_store.rs +++ b/crates/miden-tx/src/executor/data_store.rs @@ -1,9 +1,13 @@ #[cfg(feature = "async")] use alloc::boxed::Box; +use alloc::collections::BTreeSet; use miden_objects::{ - account::AccountId, block::BlockNumber, note::NoteId, transaction::TransactionInputs, + account::{Account, AccountId}, + block::{BlockHeader, BlockNumber}, + transaction::ChainMmr, }; +use vm_processor::{MastForestStore, Word}; use winter_maybe_async::*; use crate::DataStoreError; @@ -14,27 +18,24 @@ use crate::DataStoreError; /// The [DataStore] trait defines the interface that transaction objects use to fetch data /// required for transaction execution. #[maybe_async_trait] -pub trait DataStore { - /// Returns account, chain, and input note data required to execute a transaction against - /// the account with the specified ID and consuming the set of specified input notes. +pub trait DataStore: MastForestStore { + /// Returns all the data required to execute a transaction against the account with the + /// specified ID and consuming input notes created in blocks in the input `ref_blocks` set. /// - /// block_ref must be the block number of the block by which all of the input notes have been - /// recorded in the chain. In general, it is recommended that bock_ref corresponds to the - /// latest block available in the data store. + /// The highest block number in `ref_blocks` will be the transaction reference block. In + /// general, it is recommended that the reference corresponds to the latest block available + /// in the data store. /// /// # Errors /// Returns an error if: /// - The account with the specified ID could not be found in the data store. /// - The block with the specified number could not be found in the data store. - /// - Any of the notes with the specified IDs could not be found in the data store. - /// - Any of the notes with the specified IDs were already consumed. /// - The combination of specified inputs resulted in a transaction input error. /// - The data store encountered some internal error #[maybe_async] fn get_transaction_inputs( &self, account_id: AccountId, - block_ref: BlockNumber, - notes: &[NoteId], - ) -> Result; + ref_blocks: BTreeSet, + ) -> Result<(Account, Option, BlockHeader, ChainMmr), DataStoreError>; } diff --git a/crates/miden-tx/src/executor/mast_store.rs b/crates/miden-tx/src/executor/mast_store.rs index 1509f94cb4..b9b3c79a9d 100644 --- a/crates/miden-tx/src/executor/mast_store.rs +++ b/crates/miden-tx/src/executor/mast_store.rs @@ -5,7 +5,7 @@ use miden_objects::{ Digest, account::AccountCode, assembly::mast::MastForest, - transaction::{TransactionArgs, TransactionInputs}, + transaction::{InputNote, InputNotes, TransactionArgs}, }; use vm_processor::MastForestStore; @@ -59,18 +59,29 @@ impl TransactionMastStore { /// this store. /// /// The loaded code includes: - /// - Account code for the account specified in the provided [TransactionInputs]. - /// - Note scripts for all input notes in the provided [TransactionInputs]. + /// - Account code for the account specified from the provided [AccountCode]. + /// - Note scripts for all input notes in the provided [InputNotes]. /// - Transaction script (if any) from the specified [TransactionArgs]. - pub fn load_transaction_code(&self, tx_inputs: &TransactionInputs, tx_args: &TransactionArgs) { + /// - Foreign account code (if any) from the specified [TransactionArgs]. + pub fn load_transaction_code( + &self, + account_code: &AccountCode, + input_notes: &InputNotes, + tx_args: &TransactionArgs, + ) { // load account code - self.load_account_code(tx_inputs.account().code()); + self.load_account_code(account_code); // load note script MAST into the MAST store - for note in tx_inputs.input_notes() { + for note in input_notes { self.insert(note.note().script().mast().clone()); } + // load foreign account's MAST forests + for foreign_account in tx_args.foreign_accounts() { + self.load_account_code(foreign_account.account_code()); + } + // load tx script MAST into the MAST store if let Some(tx_script) = tx_args.tx_script() { self.insert(tx_script.mast().clone()); diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index bdbb73bb0f..8ded1775b3 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -3,13 +3,15 @@ use alloc::{collections::BTreeSet, sync::Arc, vec::Vec}; use miden_lib::transaction::TransactionKernel; use miden_objects::{ Felt, MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES, ZERO, - account::{AccountCode, AccountId}, - assembly::Library, - block::BlockNumber, - note::NoteId, - transaction::{ExecutedTransaction, TransactionArgs, TransactionInputs, TransactionScript}, + account::AccountId, + block::{BlockHeader, BlockNumber}, + transaction::{ + ExecutedTransaction, ForeignAccountInputs, InputNote, InputNotes, TransactionArgs, + TransactionInputs, TransactionScript, + }, vm::StackOutputs, }; +pub use vm_processor::MastForestStore; use vm_processor::{AdviceInputs, ExecutionOptions, Process, RecAdviceProvider}; use winter_maybe_async::{maybe_async, maybe_await}; @@ -19,9 +21,6 @@ use crate::auth::TransactionAuthenticator; mod data_store; pub use data_store::DataStore; -mod mast_store; -pub use mast_store::TransactionMastStore; - // TRANSACTION EXECUTOR // ================================================================================================ @@ -29,18 +28,14 @@ pub use mast_store::TransactionMastStore; /// /// Transaction execution consists of the following steps: /// - Fetch the data required to execute a transaction from the [DataStore]. -/// - Load the code associated with the transaction into the [TransactionMastStore]. /// - Execute the transaction program and create an [ExecutedTransaction]. /// /// The transaction executor uses dynamic dispatch with trait objects for the [DataStore] and /// [TransactionAuthenticator], allowing it to be used with different backend implementations. +/// At the moment of execution, the [DataStore] is expected to provide all required MAST nodes. pub struct TransactionExecutor { data_store: Arc, - mast_store: Arc, authenticator: Option>, - /// Holds the code of all accounts loaded into this transaction executor via the - /// [Self::load_account_code()] method. - account_codes: BTreeSet, exec_options: ExecutionOptions, } @@ -58,7 +53,6 @@ impl TransactionExecutor { Self { data_store, - mast_store: Arc::new(TransactionMastStore::new()), authenticator, exec_options: ExecutionOptions::new( Some(MAX_TX_EXECUTION_CYCLES), @@ -67,7 +61,6 @@ impl TransactionExecutor { false, ) .expect("Must not fail while max cycles is more than min trace length"), - account_codes: BTreeSet::new(), } } @@ -91,27 +84,6 @@ impl TransactionExecutor { self } - // STATE MUTATORS - // -------------------------------------------------------------------------------------------- - - /// Loads the provided account code into the internal MAST forest store and adds the commitment - /// of the provided code to the commitments set. - pub fn load_account_code(&mut self, code: &AccountCode) { - // load the code mast forest to the mast store - self.mast_store.load_account_code(code); - - // store the commitment of the foreign account code in the set - self.account_codes.insert(code.clone()); - } - - /// Loads the provided library code into the internal MAST forest store. - /// - /// TODO: this is a work-around to support accounts which were complied with user-defined - /// libraries. Once Miden Assembler supports library vendoring, this should go away. - pub fn load_library(&mut self, library: &Library) { - self.mast_store.insert(library.mast_forest().clone()); - } - // TRANSACTION EXECUTION // -------------------------------------------------------------------------------------------- @@ -125,35 +97,59 @@ impl TransactionExecutor { /// # Errors: /// Returns an error if: /// - If required data can not be fetched from the [DataStore]. + /// - If the transaction arguments contain foreign account data not anchored in the reference + /// block. + /// - If any input notes were created in block numbers higher than the reference block. #[maybe_async] pub fn execute_transaction( &self, account_id: AccountId, block_ref: BlockNumber, - notes: &[NoteId], + notes: InputNotes, tx_args: TransactionArgs, ) -> Result { - let tx_inputs = - maybe_await!(self.data_store.get_transaction_inputs(account_id, block_ref, notes)) + // Validate that notes were not created after the reference, and build the set of required + // block numbers + let mut ref_blocks: BTreeSet = BTreeSet::new(); + for note in ¬es { + if let Some(location) = note.location() { + if location.block_num() > block_ref { + return Err(TransactionExecutorError::NoteBlockPastReferenceBlock( + note.id(), + block_ref, + )); + } + ref_blocks.insert(location.block_num()); + } + } + + ref_blocks.insert(block_ref); + + let (account, seed, ref_block, mmr) = + maybe_await!(self.data_store.get_transaction_inputs(account_id, ref_blocks)) .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?; + validate_account_inputs(&tx_args, &ref_block)?; + + let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, notes) + .map_err(TransactionExecutorError::InvalidTransactionInputs)?; + let (stack_inputs, advice_inputs) = - TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, None); - let advice_recorder: RecAdviceProvider = advice_inputs.into(); + TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, None) + .map_err(TransactionExecutorError::InvalidTransactionInputs)?; - // load note script MAST into the MAST store - self.mast_store.load_transaction_code(&tx_inputs, &tx_args); + let advice_recorder: RecAdviceProvider = advice_inputs.into(); let mut host = TransactionHost::new( tx_inputs.account().into(), advice_recorder, - self.mast_store.clone(), + self.data_store.clone(), self.authenticator.clone(), - self.account_codes.iter().map(|code| code.commitment()).collect(), + tx_args.foreign_account_code_commitments(), ) .map_err(TransactionExecutorError::TransactionHostCreationFailed)?; - // execute the transaction kernel + // Execute the transaction kernel let result = vm_processor::execute( &TransactionKernel::main(), stack_inputs, @@ -162,25 +158,7 @@ impl TransactionExecutor { ) .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?; - // Attempt to retrieve used account codes based on the advice map - let account_codes = self - .account_codes - .iter() - .filter_map(|code| { - tx_args - .advice_inputs() - .mapped_values(&code.commitment()) - .and(Some(code.clone())) - }) - .collect(); - - build_executed_transaction( - tx_args, - tx_inputs, - result.stack_outputs().clone(), - host, - account_codes, - ) + build_executed_transaction(tx_args, tx_inputs, result.stack_outputs().clone(), host) } // SCRIPT EXECUTION @@ -201,26 +179,35 @@ impl TransactionExecutor { block_ref: BlockNumber, tx_script: TransactionScript, advice_inputs: AdviceInputs, + foreign_account_inputs: Vec, ) -> Result<[Felt; 16], TransactionExecutorError> { - let tx_inputs = - maybe_await!(self.data_store.get_transaction_inputs(account_id, block_ref, &[])) + let ref_blocks = [block_ref].into_iter().collect(); + let (account, seed, ref_block, mmr) = + maybe_await!(self.data_store.get_transaction_inputs(account_id, ref_blocks)) .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?; + let tx_args = TransactionArgs::new( + Some(tx_script.clone()), + None, + Default::default(), + foreign_account_inputs, + ); - let tx_args = TransactionArgs::new(Some(tx_script.clone()), None, Default::default()); + validate_account_inputs(&tx_args, &ref_block)?; + + let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, Default::default()) + .map_err(TransactionExecutorError::InvalidTransactionInputs)?; let (stack_inputs, advice_inputs) = - TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_inputs)); + TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_inputs)) + .map_err(TransactionExecutorError::InvalidTransactionInputs)?; let advice_recorder: RecAdviceProvider = advice_inputs.into(); - // load transaction script MAST into the MAST store - self.mast_store.load_transaction_code(&tx_inputs, &tx_args); - let mut host = TransactionHost::new( tx_inputs.account().into(), advice_recorder, - self.mast_store.clone(), + self.data_store.clone(), self.authenticator.clone(), - self.account_codes.iter().map(|code| code.commitment()).collect(), + tx_args.foreign_account_code_commitments(), ) .map_err(TransactionExecutorError::TransactionHostCreationFailed)?; @@ -246,7 +233,6 @@ fn build_executed_transaction( tx_inputs: TransactionInputs, stack_outputs: StackOutputs, host: TransactionHost, - account_codes: Vec, ) -> Result { let (advice_recorder, account_delta, output_notes, generated_signatures, tx_progress) = host.into_parts(); @@ -290,10 +276,28 @@ fn build_executed_transaction( Ok(ExecutedTransaction::new( tx_inputs, tx_outputs, - account_codes, account_delta, tx_args, advice_witness, tx_progress.into(), )) } + +/// Validates the account inputs against the reference block header. +fn validate_account_inputs( + tx_args: &TransactionArgs, + ref_block: &BlockHeader, +) -> Result<(), TransactionExecutorError> { + // Validate that foreign account inputs are anchored in the reference block + for foreign_account in tx_args.foreign_accounts() { + let computed_account_root = foreign_account.compute_account_root().map_err(|err| { + TransactionExecutorError::InvalidAccountWitness(foreign_account.id(), err) + })?; + if computed_account_root != ref_block.account_root() { + return Err(TransactionExecutorError::ForeignAccountNotAnchoredInReference( + foreign_account.id(), + )); + } + } + Ok(()) +} diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index e080240220..55ff6776f2 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -38,9 +38,7 @@ use note_builder::OutputNoteBuilder; mod tx_progress; pub use tx_progress::TransactionProgress; -use crate::{ - auth::TransactionAuthenticator, errors::TransactionHostError, executor::TransactionMastStore, -}; +use crate::{auth::TransactionAuthenticator, errors::TransactionHostError}; // TRANSACTION HOST // ================================================================================================ @@ -56,7 +54,7 @@ pub struct TransactionHost { adv_provider: A, /// MAST store which contains the code required to execute the transaction. - mast_store: Arc, + mast_store: Arc, /// Account state changes accumulated during transaction execution. /// @@ -98,7 +96,7 @@ impl TransactionHost { pub fn new( account: AccountHeader, adv_provider: A, - mast_store: Arc, + mast_store: Arc, authenticator: Option>, mut account_code_commitments: BTreeSet, ) -> Result { diff --git a/crates/miden-tx/src/lib.rs b/crates/miden-tx/src/lib.rs index d50a3a2407..a612f8ea9a 100644 --- a/crates/miden-tx/src/lib.rs +++ b/crates/miden-tx/src/lib.rs @@ -9,13 +9,13 @@ extern crate std; pub use miden_objects::transaction::TransactionInputs; mod executor; -pub use executor::{DataStore, TransactionExecutor, TransactionMastStore}; +pub use executor::{DataStore, MastForestStore, TransactionExecutor}; pub mod host; pub use host::{TransactionHost, TransactionProgress}; mod prover; -pub use prover::{LocalTransactionProver, ProvingOptions, TransactionProver}; +pub use prover::{LocalTransactionProver, ProvingOptions, TransactionMastStore, TransactionProver}; mod verifier; pub use verifier::TransactionVerifier; diff --git a/crates/miden-tx/src/prover/mast_store.rs b/crates/miden-tx/src/prover/mast_store.rs new file mode 100644 index 0000000000..695fb965e8 --- /dev/null +++ b/crates/miden-tx/src/prover/mast_store.rs @@ -0,0 +1,109 @@ +use alloc::{collections::BTreeMap, sync::Arc}; + +use miden_lib::{MidenLib, StdLibrary, transaction::TransactionKernel, utils::sync::RwLock}; +use miden_objects::{ + Digest, + account::AccountCode, + assembly::mast::MastForest, + transaction::{InputNote, InputNotes, TransactionArgs}, +}; +use vm_processor::MastForestStore; + +// TRANSACTION MAST STORE +// ================================================================================================ + +/// A store for the code available during transaction execution. +/// +/// Transaction MAST store contains a map between procedure MAST roots and [MastForest]s containing +/// MASTs for these procedures. The VM will request [MastForest]s from the store when it encounters +/// a procedure which it doesn't have the code for. Thus, to execute a program which makes +/// references to external procedures, the store must be loaded with [MastForest]s containing these +/// procedures. +pub struct TransactionMastStore { + mast_forests: RwLock>>, +} + +#[allow(clippy::new_without_default)] +impl TransactionMastStore { + /// Returns a new [TransactionMastStore] instantiated with the default libraries. + /// + /// The default libraries include: + /// - Miden standard library (miden-stdlib). + /// - Miden rollup library (miden-lib). + /// - Transaction kernel. + pub fn new() -> Self { + let mast_forests = RwLock::new(BTreeMap::new()); + let store = Self { mast_forests }; + + // load transaction kernel MAST forest + let kernels_forest = TransactionKernel::kernel().mast_forest().clone(); + store.insert(kernels_forest); + + // load miden-stdlib MAST forest + let miden_stdlib_forest = StdLibrary::default().mast_forest().clone(); + store.insert(miden_stdlib_forest); + + // load miden lib MAST forest + let miden_lib_forest = MidenLib::default().mast_forest().clone(); + store.insert(miden_lib_forest); + + store + } + + /// Loads code required for executing a transaction with the specified inputs and args into + /// this store. + /// + /// The loaded code includes: + /// - Account code for the account specified from the provided [AccountCode]. + /// - Note scripts for all input notes in the provided [InputNotes]. + /// - Transaction script (if any) from the specified [TransactionArgs]. + /// - Foreign account code (if any) from the specified [TransactionArgs]. + pub fn load_transaction_code( + &self, + account_code: &AccountCode, + input_notes: &InputNotes, + tx_args: &TransactionArgs, + ) { + // load account code + self.load_account_code(account_code); + + // load note script MAST into the MAST store + for note in input_notes { + self.insert(note.note().script().mast().clone()); + } + + // load foreign account's MAST forests + for foreign_account in tx_args.foreign_accounts() { + self.load_account_code(foreign_account.account_code()); + } + + // load tx script MAST into the MAST store + if let Some(tx_script) = tx_args.tx_script() { + self.insert(tx_script.mast().clone()); + } + } + + /// Registers all procedures of the provided [MastForest] with this store. + pub fn insert(&self, mast_forest: Arc) { + let mut mast_forests = self.mast_forests.write(); + + // only register procedures that are local to this forest + for proc_digest in mast_forest.local_procedure_digests() { + mast_forests.insert(proc_digest, mast_forest.clone()); + } + } + + /// Loads the provided account code into this store. + fn load_account_code(&self, code: &AccountCode) { + self.insert(code.mast().clone()); + } +} + +// MAST FOREST STORE IMPLEMENTATION +// ================================================================================================ + +impl MastForestStore for TransactionMastStore { + fn get(&self, procedure_root: &Digest) -> Option> { + self.mast_forests.read().get(procedure_root).cloned() + } +} diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index 69e4f4e779..f632db7ccf 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -5,7 +5,6 @@ use alloc::{sync::Arc, vec::Vec}; use miden_lib::transaction::TransactionKernel; use miden_objects::{ account::delta::AccountUpdateDetails, - assembly::Library, transaction::{OutputNote, ProvenTransaction, ProvenTransactionBuilder, TransactionWitness}, }; pub use miden_prover::ProvingOptions; @@ -14,7 +13,9 @@ use vm_processor::MemAdviceProvider; use winter_maybe_async::*; use super::{TransactionHost, TransactionProverError}; -use crate::executor::TransactionMastStore; + +mod mast_store; +pub use mast_store::TransactionMastStore; // TRANSACTION PROVER TRAIT // ================================================================================================ @@ -55,14 +56,6 @@ impl LocalTransactionProver { proof_options, } } - - /// Loads the provided library code into the internal MAST forest store. - /// - /// TODO: this is a work-around to support accounts which were complied with user-defined - /// libraries. Once Miden Assembler supports library vendoring, this should go away. - pub fn load_library(&mut self, library: &Library) { - self.mast_store.insert(library.mast_forest().clone()); - } } impl Default for LocalTransactionProver { @@ -81,17 +74,7 @@ impl TransactionProver for LocalTransactionProver { &self, tx_witness: TransactionWitness, ) -> Result { - let TransactionWitness { - tx_inputs, - tx_args, - advice_witness, - account_codes, - } = tx_witness; - - for account_code in &account_codes { - // load the code mast forest to the mast store - self.mast_store.load_account_code(account_code); - } + let TransactionWitness { tx_inputs, tx_args, advice_witness } = tx_witness; let account = tx_inputs.account(); let input_notes = tx_inputs.input_notes(); @@ -100,18 +83,23 @@ impl TransactionProver for LocalTransactionProver { // execute and prove let (stack_inputs, advice_inputs) = - TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_witness)); + TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_witness)) + .map_err(TransactionProverError::InvalidTransactionInputs)?; let advice_provider: MemAdviceProvider = advice_inputs.into(); // load the store with account/note/tx_script MASTs - self.mast_store.load_transaction_code(&tx_inputs, &tx_args); + self.mast_store.load_transaction_code(account.code(), input_notes, &tx_args); let mut host: TransactionHost<_> = TransactionHost::new( account.into(), advice_provider, self.mast_store.clone(), None, - account_codes.iter().map(|c| c.commitment()).collect(), + tx_args + .foreign_accounts() + .iter() + .map(|acc| acc.account_code().commitment()) + .collect(), ) .map_err(TransactionProverError::TransactionHostCreationFailed)?; diff --git a/crates/miden-tx/src/testing/mock_chain/mod.rs b/crates/miden-tx/src/testing/mock_chain/mod.rs index 88ca9ad51e..fcc50d2ad3 100644 --- a/crates/miden-tx/src/testing/mock_chain/mod.rs +++ b/crates/miden-tx/src/testing/mock_chain/mod.rs @@ -11,8 +11,8 @@ use miden_lib::{ use miden_objects::{ AccountError, NoteError, ProposedBatchError, ProposedBlockError, account::{ - Account, AccountBuilder, AccountComponent, AccountDelta, AccountId, AccountIdAnchor, - AccountType, AuthSecretKey, delta::AccountUpdateDetails, + Account, AccountBuilder, AccountComponent, AccountDelta, AccountHeader, AccountId, + AccountIdAnchor, AccountType, AuthSecretKey, StorageSlot, delta::AccountUpdateDetails, }, asset::{Asset, FungibleAsset, TokenSymbol}, batch::{ProposedBatch, ProvenBatch}, @@ -22,14 +22,14 @@ use miden_objects::{ }, crypto::{ dsa::rpo_falcon512::SecretKey, - merkle::{Mmr, Smt}, + merkle::{Mmr, Smt, SmtProof}, }, note::{Note, NoteHeader, NoteId, NoteInclusionProof, NoteType, Nullifier}, testing::account_code::DEFAULT_AUTH_SCRIPT, transaction::{ - ChainMmr, ExecutedTransaction, InputNote, InputNotes, OrderedTransactionHeaders, - OutputNote, ProvenTransaction, ToInputNoteCommitments, TransactionHeader, TransactionId, - TransactionInputs, TransactionScript, + ChainMmr, ExecutedTransaction, ForeignAccountInputs, InputNote, InputNotes, + OrderedTransactionHeaders, OutputNote, ProvenTransaction, ToInputNoteCommitments, + TransactionHeader, TransactionId, TransactionInputs, TransactionScript, }, }; use rand::{Rng, SeedableRng}; @@ -621,9 +621,16 @@ impl MockChain { }; let (account, seed) = if let AccountState::New = account_state { - let last_block = self.blocks.last().expect("one block should always exist"); + let epoch_block_number = self + .blocks + .last() + .expect("one block should always exist") + .header() + .epoch_block_num(); + let account_id_anchor = + self.blocks.get(epoch_block_number.as_usize()).unwrap().header(); account_builder = - account_builder.anchor(AccountIdAnchor::try_from(last_block.header()).unwrap()); + account_builder.anchor(AccountIdAnchor::try_from(account_id_anchor).unwrap()); account_builder.build().map(|(account, seed)| (account, Some(seed))).unwrap() } else { @@ -753,6 +760,31 @@ impl MockChain { (batch_reference_block, chain_mmr) } + /// Gets foreign account inputs to execute FPI transactions. + pub fn get_foreign_account_inputs(&self, account_id: AccountId) -> ForeignAccountInputs { + let account = self.available_account(account_id); + + let account_witness = self.accounts().open(account_id); + assert_eq!(account_witness.state_commitment(), account.commitment()); + + let mut storage_map_proofs = vec![]; + for slot in account.storage().slots() { + // if there are storage maps, we populate the merkle store and advice map + if let StorageSlot::Map(map) = slot { + let proofs: Vec = map.entries().map(|(key, _)| map.open(key)).collect(); + storage_map_proofs.extend(proofs); + } + } + + ForeignAccountInputs::new( + AccountHeader::from(account), + account.storage().get_header(), + account.code().clone(), + account_witness, + storage_map_proofs, + ) + } + /// Gets the inputs for a block for the provided batches. pub fn get_block_inputs<'batch, I>( &self, diff --git a/crates/miden-tx/src/testing/mock_host.rs b/crates/miden-tx/src/testing/mock_host.rs index 121c0aeac3..1381f5c0fc 100644 --- a/crates/miden-tx/src/testing/mock_host.rs +++ b/crates/miden-tx/src/testing/mock_host.rs @@ -1,4 +1,10 @@ -use alloc::{boxed::Box, collections::BTreeMap, rc::Rc, string::ToString, sync::Arc, vec::Vec}; +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + rc::Rc, + string::ToString, + sync::Arc, +}; use miden_lib::{ errors::tx_kernel_errors::TX_KERNEL_ERRORS, @@ -38,9 +44,9 @@ impl MockHost { account: AccountHeader, advice_inputs: AdviceInputs, mast_store: Rc, - mut foreign_code_commitments: Vec, + mut foreign_code_commitments: BTreeSet, ) -> Self { - foreign_code_commitments.push(account.code_commitment()); + foreign_code_commitments.insert(account.code_commitment()); let adv_provider: MemAdviceProvider = advice_inputs.into(); let proc_index_map = AccountProcedureIndexMap::new(foreign_code_commitments, &adv_provider); diff --git a/crates/miden-tx/src/testing/tx_context/builder.rs b/crates/miden-tx/src/testing/tx_context/builder.rs index fc6d1c5ec0..dd98fb7671 100644 --- a/crates/miden-tx/src/testing/tx_context/builder.rs +++ b/crates/miden-tx/src/testing/tx_context/builder.rs @@ -3,11 +3,11 @@ use alloc::{collections::BTreeMap, vec::Vec}; -use miden_lib::transaction::TransactionKernel; +use miden_lib::{transaction::TransactionKernel, utils::word_to_masm_push_string}; use miden_objects::{ FieldElement, - account::{Account, AccountCode, AccountId}, - assembly::Assembler, + account::{Account, AccountId}, + assembly::{Assembler, Library}, asset::{Asset, FungibleAsset, NonFungibleAsset}, note::{Note, NoteExecutionHint, NoteId, NoteType}, testing::{ @@ -23,8 +23,9 @@ use miden_objects::{ note::NoteBuilder, storage::prepare_assets, }, - transaction::{OutputNote, TransactionArgs, TransactionInputs, TransactionScript}, - utils::word_to_masm_push_string, + transaction::{ + ForeignAccountInputs, OutputNote, TransactionArgs, TransactionInputs, TransactionScript, + }, vm::AdviceMap, }; use rand::{Rng, SeedableRng}; @@ -32,7 +33,7 @@ use rand_chacha::ChaCha20Rng; use vm_processor::{AdviceInputs, Felt, Word}; use super::TransactionContext; -use crate::{auth::BasicAuthenticator, testing::MockChain}; +use crate::{TransactionMastStore, auth::BasicAuthenticator, testing::MockChain}; pub type MockAuthenticator = BasicAuthenticator; @@ -49,6 +50,7 @@ pub type MockAuthenticator = BasicAuthenticator; /// ``` /// # use miden_tx::testing::TransactionContextBuilder; /// # use miden_objects::{account::AccountBuilder,Felt, FieldElement}; +/// # use miden_lib::transaction::TransactionKernel; /// let tx_context = TransactionContextBuilder::with_standard_account(Felt::ONE).build(); /// /// let code = " @@ -72,7 +74,8 @@ pub struct TransactionContextBuilder { advice_inputs: AdviceInputs, authenticator: Option, expected_output_notes: Vec, - foreign_account_codes: Vec, + foreign_account_inputs: Vec, + libraries: Vec, input_notes: Vec, tx_script: Option, note_args: BTreeMap, @@ -94,7 +97,8 @@ impl TransactionContextBuilder { advice_inputs: Default::default(), transaction_inputs: None, note_args: BTreeMap::new(), - foreign_account_codes: vec![], + foreign_account_inputs: vec![], + libraries: Default::default(), } } @@ -121,7 +125,8 @@ impl TransactionContextBuilder { tx_script: None, transaction_inputs: None, note_args: BTreeMap::new(), - foreign_account_codes: vec![], + foreign_account_inputs: vec![], + libraries: Default::default(), } } @@ -168,8 +173,8 @@ impl TransactionContextBuilder { } /// Set foreign account codes that are used by the transaction - pub fn foreign_account_codes(mut self, codes: Vec) -> Self { - self.foreign_account_codes = codes; + pub fn foreign_accounts(mut self, inputs: Vec) -> Self { + self.foreign_account_inputs = inputs; self } @@ -191,6 +196,12 @@ impl TransactionContextBuilder { self } + /// Set custom libraries to add to the MAST forest store at context creation. + pub fn libraries(mut self, libraries: Vec) -> Self { + self.libraries = libraries; + self + } + /// Defines the expected output notes pub fn expected_notes(mut self, output_notes: Vec) -> Self { let output_notes = output_notes.into_iter().filter_map(|n| match n { @@ -653,20 +664,37 @@ impl TransactionContextBuilder { }, }; - let mut tx_args = - TransactionArgs::new(self.tx_script, Some(self.note_args), AdviceMap::default()) - .with_advice_inputs(self.advice_inputs.clone()); + let mut tx_args = TransactionArgs::new( + self.tx_script, + Some(self.note_args), + AdviceMap::default(), + self.foreign_account_inputs, + ); + tx_args.extend_advice_inputs(self.advice_inputs.clone()); tx_args.extend_output_note_recipients(self.expected_output_notes.clone()); + let mast_store = { + let mast_forest_store = TransactionMastStore::new(); + mast_forest_store.load_transaction_code( + tx_inputs.account().code(), + tx_inputs.input_notes(), + &tx_args, + ); + + for custom_library in self.libraries { + mast_forest_store.insert(custom_library.mast_forest().clone()); + } + mast_forest_store + }; + TransactionContext { expected_output_notes: self.expected_output_notes, tx_args, tx_inputs, + mast_store, authenticator: self.authenticator, advice_inputs: self.advice_inputs, - assembler: self.assembler, - foreign_codes: self.foreign_account_codes, } } } diff --git a/crates/miden-tx/src/testing/tx_context/mod.rs b/crates/miden-tx/src/testing/tx_context/mod.rs index dfc6f55901..641ef4744b 100644 --- a/crates/miden-tx/src/testing/tx_context/mod.rs +++ b/crates/miden-tx/src/testing/tx_context/mod.rs @@ -1,23 +1,28 @@ #[cfg(feature = "async")] use alloc::boxed::Box; -use alloc::{rc::Rc, sync::Arc, vec::Vec}; +use alloc::{collections::BTreeSet, rc::Rc, sync::Arc, vec::Vec}; use builder::MockAuthenticator; use miden_lib::transaction::TransactionKernel; use miden_objects::{ - account::{Account, AccountCode, AccountId}, + account::{Account, AccountId}, assembly::Assembler, - block::BlockNumber, - note::{Note, NoteId}, - transaction::{ExecutedTransaction, InputNote, InputNotes, TransactionArgs, TransactionInputs}, + block::{BlockHeader, BlockNumber}, + note::Note, + transaction::{ + ChainMmr, ExecutedTransaction, InputNote, InputNotes, TransactionArgs, TransactionInputs, + }, +}; +use rand_chacha::ChaCha20Rng; +use vm_processor::{ + AdviceInputs, Digest, ExecutionError, MastForest, MastForestStore, Process, Word, }; -use vm_processor::{AdviceInputs, ExecutionError, Process}; use winter_maybe_async::*; use super::{MockHost, executor::CodeExecutor}; use crate::{ DataStore, DataStoreError, TransactionExecutor, TransactionExecutorError, TransactionMastStore, - auth::TransactionAuthenticator, + auth::{BasicAuthenticator, TransactionAuthenticator}, }; mod builder; @@ -26,7 +31,6 @@ pub use builder::TransactionContextBuilder; // TRANSACTION CONTEXT // ================================================================================================ -#[derive(Clone)] /// Represents all needed data for executing a transaction, or arbitrary code. /// /// It implements [DataStore], so transactions may be executed with @@ -35,10 +39,9 @@ pub struct TransactionContext { expected_output_notes: Vec, tx_args: TransactionArgs, tx_inputs: TransactionInputs, - foreign_codes: Vec, + mast_store: TransactionMastStore, advice_inputs: AdviceInputs, authenticator: Option, - assembler: Assembler, } impl TransactionContext { @@ -52,62 +55,67 @@ impl TransactionContext { /// /// # Errors /// Returns an error if the assembly or execution of the provided code fails. - pub fn execute_code(&self, code: &str) -> Result { + pub fn execute_code_with_assembler( + &self, + code: &str, + assembler: Assembler, + ) -> Result { let (stack_inputs, mut advice_inputs) = TransactionKernel::prepare_inputs( &self.tx_inputs, &self.tx_args, Some(self.advice_inputs.clone()), - ); + ) + .unwrap(); advice_inputs.extend(self.advice_inputs.clone()); - let mast_store = Rc::new(TransactionMastStore::new()); - let test_lib = TransactionKernel::kernel_as_library(); - mast_store.insert(test_lib.mast_forest().clone()); - let program = self - .assembler + let program = assembler .clone() .with_debug_mode(true) .assemble_program(code) .expect("compilation of the provided code failed"); - mast_store.insert(program.mast_forest().clone()); - for code in &self.foreign_codes { - mast_store.insert(code.mast()); - } + let mast_store = Rc::new(TransactionMastStore::new()); - mast_store.load_transaction_code(&self.tx_inputs, &self.tx_args); + mast_store.insert(program.mast_forest().clone()); + mast_store.insert(test_lib.mast_forest().clone()); + mast_store.load_transaction_code(self.account().code(), self.input_notes(), &self.tx_args); CodeExecutor::new(MockHost::new( self.tx_inputs.account().into(), advice_inputs, mast_store, - self.foreign_codes.iter().map(|code| code.commitment()).collect(), + self.tx_args.foreign_account_code_commitments(), )) .stack_inputs(stack_inputs) .execute_program(program) } + /// Executes arbitrary code with a testing assembler ([TransactionKernel::testing_assembler()]). + /// + /// For more information, see the docs for [TransactionContext::execute_code_with_assembler()]. + pub fn execute_code(&self, code: &str) -> Result { + let assembler = TransactionKernel::testing_assembler(); + self.execute_code_with_assembler(code, assembler) + } + /// Executes the transaction through a [TransactionExecutor] #[maybe_async] pub fn execute(self) -> Result { let account_id = self.account().id(); let block_num = self.tx_inputs().block_header().block_num(); - let notes: Vec = - self.tx_inputs().input_notes().into_iter().map(|n| n.id()).collect(); + let notes = self.tx_inputs().input_notes().clone(); + let tx_args = self.tx_args().clone(); let authenticator = self - .authenticator + .authenticator() + .cloned() .map(|auth| Arc::new(auth) as Arc); - let mut tx_executor = TransactionExecutor::new(Arc::new(self.tx_inputs), authenticator); + let tx_executor = TransactionExecutor::new(Arc::new(self), authenticator); - for code in self.foreign_codes { - tx_executor.load_account_code(&code); - } - - maybe_await!(tx_executor.execute_transaction(account_id, block_num, ¬es, self.tx_args)) + maybe_await!(tx_executor.execute_transaction(account_id, block_num, notes, tx_args)) } pub fn account(&self) -> &Account { @@ -134,24 +142,28 @@ impl TransactionContext { &self.tx_inputs } - pub fn get_data_store(&self) -> Arc { - Arc::new(self.tx_inputs().clone()) + pub fn authenticator(&self) -> Option<&BasicAuthenticator> { + self.authenticator.as_ref() } } #[maybe_async_trait] -impl DataStore for TransactionInputs { +impl DataStore for TransactionContext { #[maybe_async] fn get_transaction_inputs( &self, account_id: AccountId, - block_num: BlockNumber, - notes: &[NoteId], - ) -> Result { + _ref_blocks: BTreeSet, + ) -> Result<(Account, Option, BlockHeader, ChainMmr), DataStoreError> { assert_eq!(account_id, self.account().id()); - assert_eq!(block_num, self.block_header().block_num()); - assert_eq!(notes.len(), self.input_notes().num_notes()); + let (account, seed, header, mmr, _) = self.tx_inputs.clone().into_parts(); + + Ok((account, seed, header, mmr)) + } +} - Ok(self.clone()) +impl MastForestStore for TransactionContext { + fn get(&self, procedure_hash: &Digest) -> Option> { + self.mast_store.get(procedure_hash) } } diff --git a/crates/miden-tx/src/tests/kernel_tests/mod.rs b/crates/miden-tx/src/tests/kernel_tests/mod.rs index 285be8c390..3b1cd6d9e6 100644 --- a/crates/miden-tx/src/tests/kernel_tests/mod.rs +++ b/crates/miden-tx/src/tests/kernel_tests/mod.rs @@ -1,12 +1,15 @@ use alloc::string::String; -use miden_lib::transaction::memory::{ - NOTE_MEM_SIZE, NUM_OUTPUT_NOTES_PTR, OUTPUT_NOTE_ASSETS_OFFSET, OUTPUT_NOTE_METADATA_OFFSET, - OUTPUT_NOTE_NUM_ASSETS_OFFSET, OUTPUT_NOTE_RECIPIENT_OFFSET, OUTPUT_NOTE_SECTION_OFFSET, +use miden_lib::{ + transaction::memory::{ + NOTE_MEM_SIZE, NUM_OUTPUT_NOTES_PTR, OUTPUT_NOTE_ASSETS_OFFSET, + OUTPUT_NOTE_METADATA_OFFSET, OUTPUT_NOTE_NUM_ASSETS_OFFSET, OUTPUT_NOTE_RECIPIENT_OFFSET, + OUTPUT_NOTE_SECTION_OFFSET, + }, + utils::word_to_masm_push_string, }; use miden_objects::{ - Felt, Hasher, ONE, Word, ZERO, note::Note, testing::storage::prepare_assets, - utils::word_to_masm_push_string, vm::StackInputs, + Felt, Hasher, ONE, Word, ZERO, note::Note, testing::storage::prepare_assets, vm::StackInputs, }; use vm_processor::{ContextId, Process, ProcessState}; diff --git a/crates/miden-tx/src/tests/kernel_tests/test_account.rs b/crates/miden-tx/src/tests/kernel_tests/test_account.rs index 1b3d88810d..b71ff53a31 100644 --- a/crates/miden-tx/src/tests/kernel_tests/test_account.rs +++ b/crates/miden-tx/src/tests/kernel_tests/test_account.rs @@ -5,6 +5,7 @@ use miden_lib::{ ERR_ACCOUNT_ID_UNKNOWN_VERSION, TX_KERNEL_ERRORS, }, transaction::TransactionKernel, + utils::word_to_masm_push_string, }; use miden_objects::{ account::{ @@ -27,7 +28,7 @@ use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; use vm_processor::{Digest, ExecutionError, MemAdviceProvider, ProcessState}; -use super::{Felt, ONE, StackInputs, Word, ZERO, word_to_masm_push_string}; +use super::{Felt, ONE, StackInputs, Word, ZERO}; use crate::testing::{TransactionContextBuilder, executor::CodeExecutor}; // ACCOUNT CODE TESTS @@ -270,7 +271,7 @@ fn test_get_map_item() { let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_component( AccountMockComponent::new_with_slots( - TransactionKernel::testing_assembler(), + TransactionKernel::assembler(), vec![AccountStorage::mock_item_2().slot], ) .unwrap(), @@ -301,7 +302,12 @@ fn test_get_map_item() { map_key = word_to_masm_push_string(&key), ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); assert_eq!( @@ -431,7 +437,7 @@ fn test_set_map_item() { let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) .with_component( AccountMockComponent::new_with_slots( - TransactionKernel::testing_assembler(), + TransactionKernel::assembler(), vec![AccountStorage::mock_item_2().slot], ) .unwrap(), @@ -471,7 +477,12 @@ fn test_set_map_item() { new_value = word_to_masm_push_string(&new_value), ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); let mut new_storage_map = AccountStorage::mock_map(); diff --git a/crates/miden-tx/src/tests/kernel_tests/test_asset.rs b/crates/miden-tx/src/tests/kernel_tests/test_asset.rs index 923748e45f..7a7eb44144 100644 --- a/crates/miden-tx/src/tests/kernel_tests/test_asset.rs +++ b/crates/miden-tx/src/tests/kernel_tests/test_asset.rs @@ -1,3 +1,4 @@ +use miden_lib::utils::word_to_masm_push_string; use miden_objects::{ account::AccountId, asset::NonFungibleAsset, @@ -10,7 +11,7 @@ use miden_objects::{ }; use vm_processor::ProcessState; -use super::{Felt, Hasher, ONE, Word, word_to_masm_push_string}; +use super::{Felt, Hasher, ONE, Word}; use crate::testing::TransactionContextBuilder; #[test] diff --git a/crates/miden-tx/src/tests/kernel_tests/test_asset_vault.rs b/crates/miden-tx/src/tests/kernel_tests/test_asset_vault.rs index b87447cae7..86aed12248 100644 --- a/crates/miden-tx/src/tests/kernel_tests/test_asset_vault.rs +++ b/crates/miden-tx/src/tests/kernel_tests/test_asset_vault.rs @@ -7,7 +7,8 @@ use miden_lib::{ ERR_VAULT_NON_FUNGIBLE_ASSET_ALREADY_EXISTS, ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND, }, - transaction::memory, + transaction::{TransactionKernel, memory}, + utils::word_to_masm_push_string, }; use miden_objects::{ AssetVaultError, @@ -23,7 +24,7 @@ use miden_objects::{ }; use vm_processor::ProcessState; -use super::{Felt, ONE, Word, ZERO, word_to_masm_push_string}; +use super::{Felt, ONE, Word, ZERO}; use crate::{ assert_execution_error, testing::TransactionContextBuilder, tests::kernel_tests::read_root_mem_word, @@ -52,7 +53,12 @@ fn test_get_balance() { suffix = faucet_id.suffix(), ); - let process = tx_context.execute_code(&code).unwrap(); + let process = tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); assert_eq!( process.stack.get(0).as_int(), @@ -80,7 +86,10 @@ fn test_get_balance_non_fungible_fails() { suffix = faucet_id.suffix(), ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!( process, @@ -111,7 +120,12 @@ fn test_has_non_fungible_asset() { non_fungible_asset_key = word_to_masm_push_string(&non_fungible_asset.into()) ); - let process = tx_context.execute_code(&code).unwrap(); + let process = tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); assert_eq!(process.stack.get(0), ONE); } @@ -147,7 +161,12 @@ fn test_add_fungible_asset_success() { FUNGIBLE_ASSET = word_to_masm_push_string(&add_fungible_asset.into()) ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); assert_eq!( @@ -190,7 +209,10 @@ fn test_add_non_fungible_asset_fail_overflow() { FUNGIBLE_ASSET = word_to_masm_push_string(&add_fungible_asset.into()) ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED); assert!(account_vault.add_asset(add_fungible_asset).is_err()); @@ -226,7 +248,12 @@ fn test_add_non_fungible_asset_success() { FUNGIBLE_ASSET = word_to_masm_push_string(&add_non_fungible_asset.into()) ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); assert_eq!( @@ -264,7 +291,10 @@ fn test_add_non_fungible_asset_fail_duplicate() { NON_FUNGIBLE_ASSET = word_to_masm_push_string(&non_fungible_asset.into()) ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_VAULT_NON_FUNGIBLE_ASSET_ALREADY_EXISTS); assert!(account_vault.add_asset(non_fungible_asset).is_err()); @@ -302,7 +332,12 @@ fn test_remove_fungible_asset_success_no_balance_remaining() { FUNGIBLE_ASSET = word_to_masm_push_string(&remove_fungible_asset.into()) ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); assert_eq!( @@ -343,7 +378,10 @@ fn test_remove_fungible_asset_fail_remove_too_much() { FUNGIBLE_ASSET = word_to_masm_push_string(&remove_fungible_asset.into()) ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW); } @@ -380,7 +418,12 @@ fn test_remove_fungible_asset_success_balance_remaining() { FUNGIBLE_ASSET = word_to_masm_push_string(&remove_fungible_asset.into()) ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); assert_eq!( @@ -425,7 +468,10 @@ fn test_remove_inexisting_non_fungible_asset_fails() { FUNGIBLE_ASSET = word_to_masm_push_string(&non_existent_non_fungible_asset.into()) ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND); assert_matches!( @@ -462,7 +508,12 @@ fn test_remove_non_fungible_asset_success() { FUNGIBLE_ASSET = word_to_masm_push_string(&non_fungible_asset.into()) ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); assert_eq!( diff --git a/crates/miden-tx/src/tests/kernel_tests/test_epilogue.rs b/crates/miden-tx/src/tests/kernel_tests/test_epilogue.rs index 2a4675a3bd..9db36708cd 100644 --- a/crates/miden-tx/src/tests/kernel_tests/test_epilogue.rs +++ b/crates/miden-tx/src/tests/kernel_tests/test_epilogue.rs @@ -55,7 +55,12 @@ fn test_epilogue() { " ); - let process = tx_context.execute_code(&code).unwrap(); + let process = tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let final_account = Account::mock( tx_context.account().id().into(), @@ -120,7 +125,12 @@ fn test_compute_output_note_id() { " ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); assert_eq!( note.assets().commitment().as_elements(), @@ -171,7 +181,10 @@ fn test_epilogue_asset_preservation_violation_too_few_input() { " ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME); } @@ -205,7 +218,10 @@ fn test_epilogue_asset_preservation_violation_too_many_fungible_input() { " ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_EPILOGUE_TOTAL_NUMBER_OF_ASSETS_MUST_STAY_THE_SAME); } @@ -242,7 +258,12 @@ fn test_block_expiration_height_monotonically_decreases() { .replace("{value_2}", &v2.to_string()) .replace("{min_value}", &v2.min(v1).to_string()); - let process = &tx_context.execute_code(code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); // Expiry block should be set to transaction's block + the stored expiration delta @@ -269,7 +290,10 @@ fn test_invalid_expiration_deltas() { for value in test_values { let code = &code_template.replace("{value_1}", &value.to_string()); - let process = tx_context.execute_code(code); + let process = tx_context.execute_code_with_assembler( + code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_TX_INVALID_EXPIRATION_DELTA); } @@ -296,7 +320,12 @@ fn test_no_expiration_delta_set() { end "; - let process = &tx_context.execute_code(code_template).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + code_template, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); // Default value should be equal to u32::max, set in the prologue @@ -341,7 +370,12 @@ fn test_epilogue_increment_nonce_success() { " ); - tx_context.execute_code(&code).unwrap(); + tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); } #[test] @@ -379,6 +413,9 @@ fn test_epilogue_increment_nonce_violation() { " ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_ACCOUNT_NONCE_DID_NOT_INCREASE_AFTER_STATE_CHANGE) } diff --git a/crates/miden-tx/src/tests/kernel_tests/test_faucet.rs b/crates/miden-tx/src/tests/kernel_tests/test_faucet.rs index 1617000f16..5b20f041b0 100644 --- a/crates/miden-tx/src/tests/kernel_tests/test_faucet.rs +++ b/crates/miden-tx/src/tests/kernel_tests/test_faucet.rs @@ -7,7 +7,8 @@ use miden_lib::{ ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN, ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW, }, - transaction::memory::NATIVE_ACCT_STORAGE_SLOTS_SECTION_PTR, + transaction::{TransactionKernel, memory::NATIVE_ACCT_STORAGE_SLOTS_SECTION_PTR}, + utils::word_to_masm_push_string, }; use miden_objects::{ FieldElement, @@ -25,9 +26,8 @@ use miden_objects::{ storage::FAUCET_STORAGE_DATA_SLOT, }, }; -use vm_processor::{Felt, ProcessState}; +use vm_processor::{Felt, ONE, ProcessState}; -use super::{ONE, word_to_masm_push_string}; use crate::{assert_execution_error, testing::TransactionContextBuilder}; // FUNGIBLE FAUCET MINT TESTS @@ -72,7 +72,12 @@ fn test_mint_fungible_asset_succeeds() { suffix = faucet_id.suffix(), ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); let expected_final_storage_amount = FUNGIBLE_FAUCET_INITIAL_BALANCE + FUNGIBLE_ASSET_AMOUNT; @@ -109,7 +114,10 @@ fn test_mint_fungible_asset_fails_not_faucet_account() { suffix = faucet_id.suffix(), ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); } @@ -134,7 +142,10 @@ fn test_mint_fungible_asset_inconsistent_faucet_id() { suffix = faucet_id.suffix(), ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); } @@ -165,7 +176,10 @@ fn test_mint_fungible_asset_fails_saturate_max_amount() { saturating_amount = FungibleAsset::MAX_AMOUNT - FUNGIBLE_FAUCET_INITIAL_BALANCE + 1 ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_FAUCET_NEW_TOTAL_SUPPLY_WOULD_EXCEED_MAX_ASSET_AMOUNT); } @@ -225,7 +239,12 @@ fn test_mint_non_fungible_asset_succeeds() { asset_vault_key = word_to_masm_push_string(&StorageMap::hash_key(asset_vault_key.into())), ); - tx_context.execute_code(&code).unwrap(); + tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); } #[test] @@ -248,7 +267,10 @@ fn test_mint_non_fungible_asset_fails_not_faucet_account() { non_fungible_asset = word_to_masm_push_string(&non_fungible_asset.into()) ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); } @@ -273,7 +295,10 @@ fn test_mint_non_fungible_asset_fails_inconsistent_faucet_id() { non_fungible_asset = word_to_masm_push_string(&non_fungible_asset.into()) ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_NON_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); } @@ -303,7 +328,10 @@ fn test_mint_non_fungible_asset_fails_asset_already_exists() { non_fungible_asset = word_to_masm_push_string(&non_fungible_asset.into()) ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_FAUCET_NON_FUNGIBLE_ASSET_ALREADY_ISSUED); } @@ -353,7 +381,12 @@ fn test_burn_fungible_asset_succeeds() { final_input_vault_asset_amount = CONSUMED_ASSET_1_AMOUNT - FUNGIBLE_ASSET_AMOUNT, ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); let expected_final_storage_amount = FUNGIBLE_FAUCET_INITIAL_BALANCE - FUNGIBLE_ASSET_AMOUNT; @@ -390,7 +423,10 @@ fn test_burn_fungible_asset_fails_not_faucet_account() { suffix = faucet_id.suffix(), ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); } @@ -421,7 +457,10 @@ fn test_burn_fungible_asset_inconsistent_faucet_id() { suffix = faucet_id.suffix(), ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN); } @@ -453,7 +492,10 @@ fn test_burn_fungible_asset_insufficient_input_amount() { saturating_amount = CONSUMED_ASSET_1_AMOUNT + 1 ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_VAULT_FUNGIBLE_ASSET_AMOUNT_LESS_THAN_AMOUNT_TO_WITHDRAW); } @@ -518,7 +560,12 @@ fn test_burn_non_fungible_asset_succeeds() { burnt_asset_vault_key = word_to_masm_push_string(&burnt_asset_vault_key), ); - tx_context.execute_code(&code).unwrap(); + tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); } #[test] @@ -547,7 +594,10 @@ fn test_burn_non_fungible_asset_fails_does_not_exist() { non_fungible_asset = word_to_masm_push_string(&non_fungible_asset_burnt.into()) ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_FAUCET_NON_FUNGIBLE_ASSET_TO_BURN_NOT_FOUND); } @@ -573,7 +623,10 @@ fn test_burn_non_fungible_asset_fails_not_faucet_account() { non_fungible_asset = word_to_masm_push_string(&non_fungible_asset_burnt.into()) ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!( process, @@ -608,7 +661,10 @@ fn test_burn_non_fungible_asset_fails_inconsistent_faucet_id() { non_fungible_asset = word_to_masm_push_string(&non_fungible_asset_burnt.into()) ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_FAUCET_NON_FUNGIBLE_ASSET_TO_BURN_NOT_FOUND); } @@ -657,7 +713,12 @@ fn test_is_non_fungible_asset_issued_succeeds() { non_fungible_asset_2 = word_to_masm_push_string(&non_fungible_asset_2.into()), ); - tx_context.execute_code(&code).unwrap(); + tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); } // GET TOTAL ISSUANCE TESTS @@ -691,5 +752,10 @@ fn test_get_total_issuance_succeeds() { ", ); - tx_context.execute_code(&code).unwrap(); + tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); } diff --git a/crates/miden-tx/src/tests/kernel_tests/test_fpi.rs b/crates/miden-tx/src/tests/kernel_tests/test_fpi.rs index a370201e14..cc4b7f643e 100644 --- a/crates/miden-tx/src/tests/kernel_tests/test_fpi.rs +++ b/crates/miden-tx/src/tests/kernel_tests/test_fpi.rs @@ -18,11 +18,11 @@ use miden_lib::{ use miden_objects::{ FieldElement, account::{ - Account, AccountBuilder, AccountComponent, AccountProcedureInfo, AccountStorage, - StorageSlot, + Account, AccountBuilder, AccountComponent, AccountHeader, AccountProcedureInfo, + AccountStorage, StorageSlot, }, testing::{account_component::AccountMockComponent, storage::STORAGE_LEAVES_2}, - transaction::TransactionScript, + transaction::{ForeignAccountInputs, TransactionScript}, }; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; @@ -30,7 +30,7 @@ use vm_processor::{AdviceInputs, Felt}; use super::{Process, Word, ZERO}; use crate::{ - TransactionExecutor, TransactionExecutorError, assert_execution_error, + TransactionExecutorError, assert_execution_error, testing::MockChain, tests::kernel_tests::{read_root_mem_word, try_read_root_mem_word}, }; @@ -94,12 +94,11 @@ fn test_fpi_memory() { let mut mock_chain = MockChain::with_accounts(&[native_account.clone(), foreign_account.clone()]); mock_chain.seal_next_block(); - let advice_inputs = get_mock_fpi_adv_inputs(vec![&foreign_account], &mock_chain); + let fpi_inputs = mock_chain.get_foreign_account_inputs(foreign_account.id()); let tx_context = mock_chain .build_tx_context(native_account.id(), &[], &[]) - .foreign_account_codes(vec![foreign_account.code().clone()]) - .advice_inputs(advice_inputs.clone()) + .foreign_accounts(vec![fpi_inputs]) .build(); // GET ITEM @@ -358,16 +357,13 @@ fn test_fpi_memory_two_accounts() { foreign_account_2.clone(), ]); mock_chain.seal_next_block(); - let advice_inputs = - get_mock_fpi_adv_inputs(vec![&foreign_account_1, &foreign_account_2], &mock_chain); + let foreign_account_inputs_1 = mock_chain.get_foreign_account_inputs(foreign_account_1.id()); + + let foreign_account_inputs_2 = mock_chain.get_foreign_account_inputs(foreign_account_2.id()); let tx_context = mock_chain .build_tx_context(native_account.id(), &[], &[]) - .foreign_account_codes(vec![ - foreign_account_1.code().clone(), - foreign_account_2.code().clone(), - ]) - .advice_inputs(advice_inputs.clone()) + .foreign_accounts(vec![foreign_account_inputs_1, foreign_account_inputs_2]) .build(); // GET ITEM TWICE WITH TWO ACCOUNTS @@ -554,7 +550,6 @@ fn test_fpi_execute_foreign_procedure() { let mut mock_chain = MockChain::with_accounts(&[native_account.clone(), foreign_account.clone()]); mock_chain.seal_next_block(); - let advice_inputs = get_mock_fpi_adv_inputs(vec![&foreign_account], &mock_chain); let code = format!( " @@ -624,24 +619,14 @@ fn test_fpi_execute_foreign_procedure() { let tx_script = TransactionScript::compile(code, vec![], TransactionKernel::testing_assembler()).unwrap(); + let foreign_account_inputs = mock_chain.get_foreign_account_inputs(foreign_account.id()); let tx_context = mock_chain .build_tx_context(native_account.id(), &[], &[]) - .advice_inputs(advice_inputs.clone()) + .foreign_accounts(vec![foreign_account_inputs]) .tx_script(tx_script) .build(); - let block_ref = tx_context.tx_inputs().block_header().block_num(); - - let mut executor = TransactionExecutor::new(tx_context.get_data_store(), None).with_tracing(); - - // load the mast forest of the foreign account's code to be able to create an account procedure - // index map and execute the specified foreign procedure - executor.load_account_code(foreign_account.code()); - - let _executed_transaction = executor - .execute_transaction(native_account.id(), block_ref, &[], tx_context.tx_args().clone()) - .map_err(|e| e.to_string()) - .unwrap(); + let _executed_transaction = tx_context.execute().map_err(|e| e.to_string()).unwrap(); } // NESTED FPI TESTS @@ -785,16 +770,20 @@ fn test_nested_fpi_cyclic_invocation() { second_foreign_account.clone(), ]); mock_chain.seal_block(None, None); - let mut advice_inputs = - get_mock_fpi_adv_inputs(vec![&first_foreign_account, &second_foreign_account], &mock_chain); + let foreign_account_inputs = vec![ + mock_chain.get_foreign_account_inputs(first_foreign_account.id()), + mock_chain.get_foreign_account_inputs(second_foreign_account.id()), + ]; // push the hashes of the foreign procedures and account IDs to the advice stack to be able to // call them dynamically. + let mut advice_inputs = AdviceInputs::default(); advice_inputs.extend_stack(*second_foreign_account.code().procedures()[0].mast_root()); advice_inputs.extend_stack([ second_foreign_account.id().suffix(), second_foreign_account.id().prefix().as_felt(), ]); + advice_inputs.extend_stack(*first_foreign_account.code().procedures()[1].mast_root()); advice_inputs.extend_stack([ first_foreign_account.id().suffix(), @@ -846,25 +835,12 @@ fn test_nested_fpi_cyclic_invocation() { let tx_context = mock_chain .build_tx_context(native_account.id(), &[], &[]) - .advice_inputs(advice_inputs.clone()) + .foreign_accounts(foreign_account_inputs) + .advice_inputs(advice_inputs) .tx_script(tx_script) .build(); - let block_ref = tx_context.tx_inputs().block_header().block_num(); - - let mut executor = TransactionExecutor::new(tx_context.get_data_store(), None) - .with_tracing() - .with_debug_mode(); - - // load the mast forest of the foreign account's code to be able to create an account procedure - // index map and execute the specified foreign procedure - executor.load_account_code(first_foreign_account.code()); - executor.load_account_code(second_foreign_account.code()); - - let _executed_transaction = executor - .execute_transaction(native_account.id(), block_ref, &[], tx_context.tx_args().clone()) - .map_err(|e| e.to_string()) - .unwrap(); + let _executed_transaction = tx_context.execute().map_err(|e| e.to_string()).unwrap(); } /// Test that code will panic in attempt to create more than 63 foreign accounts. @@ -874,14 +850,16 @@ fn test_nested_fpi_cyclic_invocation() { #[test] fn test_nested_fpi_stack_overflow() { // use a custom thread to increase its stack capacity - std::thread::Builder::new().stack_size(8 * 1_048_576).spawn(||{ - let mut foreign_accounts = Vec::new(); + std::thread::Builder::new() + .stack_size(8 * 1_048_576) + .spawn(|| { + let mut foreign_accounts = Vec::new(); - let last_foreign_account_code_source = " + let last_foreign_account_code_source = " use.miden::account export.get_item_foreign - # make this foreign procedure unique to make sure that we invoke the procedure + # make this foreign procedure unique to make sure that we invoke the procedure # of the foreign account, not the native one push.1 drop @@ -898,26 +876,27 @@ fn test_nested_fpi_stack_overflow() { end "; - let storage_slots = vec![AccountStorage::mock_item_0().slot]; - let last_foreign_account_component = AccountComponent::compile( - last_foreign_account_code_source, - TransactionKernel::testing_assembler(), - storage_slots, - ) - .unwrap() - .with_supports_all_types(); + let storage_slots = vec![AccountStorage::mock_item_0().slot]; + let last_foreign_account_component = AccountComponent::compile( + last_foreign_account_code_source, + TransactionKernel::testing_assembler(), + storage_slots, + ) + .unwrap() + .with_supports_all_types(); - let last_foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) - .with_component(last_foreign_account_component) - .build_existing() - .unwrap(); + let last_foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_component(last_foreign_account_component) + .build_existing() + .unwrap(); - foreign_accounts.push(last_foreign_account); + foreign_accounts.push(last_foreign_account); - for foreign_account_index in 0..63 { - let next_account = foreign_accounts.last().unwrap(); + for foreign_account_index in 0..63 { + let next_account = foreign_accounts.last().unwrap(); - let foreign_account_code_source = format!(" + let foreign_account_code_source = format!( + " use.miden::tx use.std::sys @@ -938,48 +917,53 @@ fn test_nested_fpi_stack_overflow() { exec.sys::truncate_stack end - ", - next_account_proc_hash = next_account.code().procedures()[0].mast_root(), - next_foreign_suffix = next_account.id().suffix(), - next_foreign_prefix = next_account.id().prefix().as_felt(), - ); - - let foreign_account_component = AccountComponent::compile( - foreign_account_code_source, - TransactionKernel::testing_assembler(), - vec![], - ) - .unwrap() - .with_supports_all_types(); + ", + next_account_proc_hash = next_account.code().procedures()[0].mast_root(), + next_foreign_suffix = next_account.id().suffix(), + next_foreign_prefix = next_account.id().prefix().as_felt(), + ); + + let foreign_account_component = AccountComponent::compile( + foreign_account_code_source, + TransactionKernel::testing_assembler(), + vec![], + ) + .unwrap() + .with_supports_all_types(); + + let foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_component(foreign_account_component) + .build_existing() + .unwrap(); + + foreign_accounts.push(foreign_account) + } - let foreign_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) - .with_component(foreign_account_component) + // ------ NATIVE ACCOUNT --------------------------------------------------------------- + let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) + .with_component( + AccountMockComponent::new_with_slots( + TransactionKernel::testing_assembler(), + vec![], + ) + .unwrap(), + ) .build_existing() .unwrap(); - foreign_accounts.push(foreign_account) - } - - // ------ NATIVE ACCOUNT --------------------------------------------------------------- - let native_account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) - .with_component( - AccountMockComponent::new_with_slots(TransactionKernel::testing_assembler(), vec![]) - .unwrap(), - ) - .build_existing() - .unwrap(); - - let mut mock_chain = MockChain::with_accounts(&[ - vec![native_account.clone()], foreign_accounts.clone() - ].concat()); + let mut mock_chain = MockChain::with_accounts( + &[vec![native_account.clone()], foreign_accounts.clone()].concat(), + ); - mock_chain.seal_block(None, None); + mock_chain.seal_block(None, None); - let advice_inputs = - get_mock_fpi_adv_inputs(foreign_accounts.iter().collect::>(), &mock_chain); + let foreign_accounts: Vec = foreign_accounts + .iter() + .map(|acc| mock_chain.get_foreign_account_inputs(acc.id())) + .collect(); - let code = format!( - " + let code = format!( + " use.std::sys use.miden::tx @@ -1002,50 +986,37 @@ fn test_nested_fpi_stack_overflow() { exec.sys::truncate_stack end ", - foreign_account_proc_hash = foreign_accounts.last().unwrap().code().procedures()[0].mast_root(), - foreign_prefix = foreign_accounts.last().unwrap().id().prefix().as_felt(), - foreign_suffix = foreign_accounts.last().unwrap().id().suffix(), - ); + foreign_account_proc_hash = + foreign_accounts.last().unwrap().account_code().procedures()[0].mast_root(), + foreign_prefix = foreign_accounts.last().unwrap().id().prefix().as_felt(), + foreign_suffix = foreign_accounts.last().unwrap().id().suffix(), + ); - let tx_script = TransactionScript::compile( - code, - vec![], - TransactionKernel::testing_assembler().with_debug_mode(true), - ) - .unwrap(); + let tx_script = TransactionScript::compile( + code, + vec![], + TransactionKernel::testing_assembler().with_debug_mode(true), + ) + .unwrap(); + + let tx_context = mock_chain + .build_tx_context(native_account.id(), &[], &[]) + .foreign_accounts(foreign_accounts) + .tx_script(tx_script) + .build(); - let tx_context = mock_chain - .build_tx_context(native_account.id(), &[], &[]) - .advice_inputs(advice_inputs.clone()) - .tx_script(tx_script) - .build(); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - - let mut executor = TransactionExecutor::new(tx_context.get_data_store(), None) - .with_tracing() - .with_debug_mode(); - - // load the mast forest of the foreign account's code to be able to create an account - // procedure index map and execute the specified foreign procedure - for foreign_account in foreign_accounts { - executor.load_account_code(foreign_account.code()); - } - - let err = executor - .execute_transaction( - native_account.id(), - block_ref, - &[], - tx_context.tx_args().clone(), - ).unwrap_err(); - - let TransactionExecutorError::TransactionProgramExecutionFailed(err) = err else { - panic!("unexpected error") - }; - - assert_execution_error!(Err::<(), _>(err), ERR_FOREIGN_ACCOUNT_MAX_NUMBER_EXCEEDED); - }).expect("thread panic external").join().expect("thread panic internal"); + let err = tx_context.execute() + .unwrap_err(); + + let TransactionExecutorError::TransactionProgramExecutionFailed(err) = err else { + panic!("unexpected error") + }; + + assert_execution_error!(Err::<(), _>(err), ERR_FOREIGN_ACCOUNT_MAX_NUMBER_EXCEEDED); + }) + .expect("thread panic external") + .join() + .expect("thread panic internal"); } /// Test that code will panic in attempt to call a procedure from the native account. @@ -1101,10 +1072,11 @@ fn test_nested_fpi_native_account_invocation() { let mut mock_chain = MockChain::with_accounts(&[native_account.clone(), foreign_account.clone()]); mock_chain.seal_block(None, None); - let mut advice_inputs = get_mock_fpi_adv_inputs(vec![&foreign_account], &mock_chain); + let foreign_account_inputs = mock_chain.get_foreign_account_inputs(foreign_account.id()); // push the hash of the native procedure and native account IDs to the advice stack to be able // to call them dynamically. + let mut advice_inputs = AdviceInputs::default(); advice_inputs.extend_stack(*native_account.code().procedures()[2].mast_root()); advice_inputs .extend_stack([native_account.id().suffix(), native_account.id().prefix().as_felt()]); @@ -1147,23 +1119,12 @@ fn test_nested_fpi_native_account_invocation() { let tx_context = mock_chain .build_tx_context(native_account.id(), &[], &[]) - .advice_inputs(advice_inputs.clone()) + .foreign_accounts(vec![foreign_account_inputs]) + .advice_inputs(advice_inputs) .tx_script(tx_script) .build(); - let block_ref = tx_context.tx_inputs().block_header().block_num(); - - let mut executor = TransactionExecutor::new(tx_context.get_data_store(), None) - .with_tracing() - .with_debug_mode(); - - // load the mast forest of the foreign account's code to be able to create an account procedure - // index map and execute the specified foreign procedure - executor.load_account_code(foreign_account.code()); - - let err = executor - .execute_transaction(native_account.id(), block_ref, &[], tx_context.tx_args().clone()) - .unwrap_err(); + let err = tx_context.execute().unwrap_err(); let TransactionExecutorError::TransactionProgramExecutionFailed(err) = err else { panic!("unexpected error") @@ -1226,15 +1187,27 @@ fn test_fpi_stale_account() { .unwrap(); // Place the modified account in the advice provider, which will cause the commitment mismatch. - let advice_inputs = get_mock_fpi_adv_inputs(vec![&foreign_account], &mock_chain); + let foreign_account_inputs = mock_chain.get_foreign_account_inputs(foreign_account.id()); + + // We want to create a mixed ForeignAccountInputs because we want to have a valid account + // witness against the ref block, but have newer account data (ie, a new state). Otherwise, + // any non-validity of the account witness is caught in + // TransactionExecutor::execute_transaction() (see `test_fpi_anchoring_validations()` for + // context on this check) + let overridden_foreign_account_inputs = ForeignAccountInputs::new( + AccountHeader::from(foreign_account.clone()), + foreign_account.storage().get_header(), + foreign_account.code().clone(), + foreign_account_inputs.witness().clone(), + foreign_account_inputs.storage_map_proofs().to_vec(), + ); // The account tree from which the transaction inputs are fetched here has the state from the // original unmodified foreign account. This should result in the foreign account's proof to be // invalid for this account tree root. let tx_context = mock_chain .build_tx_context(native_account.id(), &[], &[]) - .foreign_account_codes(vec![foreign_account.code().clone()]) - .advice_inputs(advice_inputs.clone()) + .foreign_accounts(vec![overridden_foreign_account_inputs]) .build(); // Attempt to run FPI. @@ -1276,40 +1249,6 @@ fn test_fpi_stale_account() { // HELPER FUNCTIONS // ================================================================================================ -fn get_mock_fpi_adv_inputs( - foreign_accounts: Vec<&Account>, - mock_chain: &MockChain, -) -> AdviceInputs { - let mut advice_inputs = AdviceInputs::default(); - - for foreign_account in foreign_accounts { - TransactionKernel::extend_advice_inputs_for_account( - &mut advice_inputs, - &foreign_account.into(), - foreign_account.code(), - &foreign_account.storage().get_header(), - // Provide the merkle path of the foreign account to be able to verify that the account - // tree has the commitment of this foreign account. Verification is done during the - // execution of the `kernel::account::validate_current_foreign_account` procedure. - &mock_chain.accounts().open(foreign_account.id()), - ) - .unwrap(); - - for slot in foreign_account.storage().slots() { - // if there are storage maps, we populate the merkle store and advice map - if let StorageSlot::Map(map) = slot { - // extend the merkle store and map with the storage maps - advice_inputs.extend_merkle_store(map.inner_nodes()); - // populate advice map with Sparse Merkle Tree leaf nodes - advice_inputs - .extend_map(map.leaves().map(|(_, leaf)| (leaf.hash(), leaf.to_elements()))); - } - } - } - - advice_inputs -} - fn foreign_account_data_memory_assertions(foreign_account: &Account, process: &Process) { let foreign_account_data_ptr = NATIVE_ACCOUNT_DATA_PTR + ACCOUNT_DATA_LENGTH as u32; diff --git a/crates/miden-tx/src/tests/kernel_tests/test_note.rs b/crates/miden-tx/src/tests/kernel_tests/test_note.rs index ae53363e73..8fab39c6ae 100644 --- a/crates/miden-tx/src/tests/kernel_tests/test_note.rs +++ b/crates/miden-tx/src/tests/kernel_tests/test_note.rs @@ -1,4 +1,4 @@ -use alloc::{collections::BTreeMap, string::String}; +use alloc::{collections::BTreeMap, string::String, vec::Vec}; use miden_lib::{ errors::tx_kernel_errors::ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SENDER_FROM_INCORRECT_CONTEXT, @@ -11,7 +11,7 @@ use miden_objects::{ Note, NoteExecutionHint, NoteExecutionMode, NoteInputs, NoteMetadata, NoteTag, NoteType, }, testing::{account_id::ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, note::NoteBuilder}, - transaction::TransactionArgs, + transaction::{ForeignAccountInputs, TransactionArgs}, }; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; @@ -375,6 +375,7 @@ fn test_note_script_and_note_args() { None, Some(note_args_map), tx_context.tx_args().advice_inputs().clone().map, + Vec::::new(), ); tx_context.set_tx_args(tx_args); @@ -622,29 +623,29 @@ pub fn test_timelock() { const TIMESTAMP_ERROR: u32 = 123; let code = format!( - " + " use.miden::note use.miden::tx - begin - # store the note inputs to memory starting at address 0 - push.0 exec.note::get_inputs - # => [num_inputs, inputs_ptr] + begin + # store the note inputs to memory starting at address 0 + push.0 exec.note::get_inputs + # => [num_inputs, inputs_ptr] - # make sure the number of inputs is 1 - eq.1 assert.err=789 - # => [inputs_ptr] + # make sure the number of inputs is 1 + eq.1 assert.err=789 + # => [inputs_ptr] - # read the timestamp at which the note can be consumed - mem_load - # => [timestamp] + # read the timestamp at which the note can be consumed + mem_load + # => [timestamp] - exec.tx::get_block_timestamp - # => [block_timestamp, timestamp] + exec.tx::get_block_timestamp + # => [block_timestamp, timestamp] + # ensure block timestamp is newer than timestamp - # ensure block timestamp is newer than timestamp - lte assert.err={TIMESTAMP_ERROR} - # => [] + lte assert.err={TIMESTAMP_ERROR} + # => [] end" ); diff --git a/crates/miden-tx/src/tests/kernel_tests/test_prologue.rs b/crates/miden-tx/src/tests/kernel_tests/test_prologue.rs index 8587e33480..2d79480134 100644 --- a/crates/miden-tx/src/tests/kernel_tests/test_prologue.rs +++ b/crates/miden-tx/src/tests/kernel_tests/test_prologue.rs @@ -39,7 +39,7 @@ use miden_objects::{ account_id::{ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET}, constants::FUNGIBLE_FAUCET_INITIAL_BALANCE, }, - transaction::{TransactionArgs, TransactionScript}, + transaction::{ForeignAccountInputs, TransactionArgs, TransactionScript}, }; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; @@ -95,6 +95,7 @@ fn test_transaction_prologue() { Some(tx_script), Some(note_args_map), tx_context.tx_args().advice_inputs().clone().map, + Vec::::new(), ); tx_context.set_tx_args(tx_args); diff --git a/crates/miden-tx/src/tests/kernel_tests/test_tx.rs b/crates/miden-tx/src/tests/kernel_tests/test_tx.rs index db3e7b4ca5..46289e6047 100644 --- a/crates/miden-tx/src/tests/kernel_tests/test_tx.rs +++ b/crates/miden-tx/src/tests/kernel_tests/test_tx.rs @@ -1,36 +1,120 @@ -use alloc::vec::Vec; -use std::string::String; +use alloc::{string::String, sync::Arc, vec::Vec}; use miden_lib::{ errors::tx_kernel_errors::{ ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS, ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT, }, - transaction::memory::{ - NOTE_MEM_SIZE, NUM_OUTPUT_NOTES_PTR, OUTPUT_NOTE_ASSETS_OFFSET, - OUTPUT_NOTE_METADATA_OFFSET, OUTPUT_NOTE_RECIPIENT_OFFSET, OUTPUT_NOTE_SECTION_OFFSET, + transaction::{ + TransactionKernel, + memory::{ + NOTE_MEM_SIZE, NUM_OUTPUT_NOTES_PTR, OUTPUT_NOTE_ASSETS_OFFSET, + OUTPUT_NOTE_METADATA_OFFSET, OUTPUT_NOTE_RECIPIENT_OFFSET, OUTPUT_NOTE_SECTION_OFFSET, + }, }, + utils::word_to_masm_push_string, }; use miden_objects::{ FieldElement, account::AccountId, asset::NonFungibleAsset, + block::BlockNumber, note::{ Note, NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteInputs, NoteMetadata, NoteRecipient, NoteTag, NoteType, }, testing::{ - account_id::{ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2}, + account_id::{ + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2, + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, + }, constants::NON_FUNGIBLE_ASSET_DATA_2, }, - transaction::{OutputNote, OutputNotes}, + transaction::{InputNotes, OutputNote, OutputNotes, TransactionArgs}, }; -use super::{Felt, ONE, ProcessState, Word, ZERO, word_to_masm_push_string}; +use super::{Felt, ONE, ProcessState, Word, ZERO}; use crate::{ - assert_execution_error, testing::TransactionContextBuilder, + TransactionExecutor, TransactionExecutorError, assert_execution_error, + testing::{Auth, MockChain, TransactionContextBuilder}, tests::kernel_tests::read_root_mem_word, }; +#[test] +fn test_fpi_anchoring_validations() { + // Create a chain with an account + let mut mock_chain = MockChain::new(); + let account = mock_chain.add_existing_wallet(Auth::BasicAuth, vec![]); + mock_chain.seal_next_block(); + + // Retrieve inputs which will become stale + let inputs = mock_chain.get_foreign_account_inputs(account.id()); + + // Add account to modify account tree + let new_account = mock_chain.add_existing_wallet(Auth::BasicAuth, vec![]); + mock_chain.seal_next_block(); + + // Attempt to execute with older foreign account inputs + let transaction = mock_chain + .build_tx_context(new_account.id(), &[], &[]) + .foreign_accounts(vec![inputs]) + .build() + .execute(); + + assert_matches::assert_matches!( + transaction, + Err(TransactionExecutorError::ForeignAccountNotAnchoredInReference(_)) + ); +} + +#[test] +fn test_future_input_note_fails() { + // Create a chain with an account + let mut mock_chain = MockChain::new(); + let account = mock_chain.add_existing_wallet(Auth::BasicAuth, vec![]); + mock_chain.seal_block(Some(10), None); + + // Create note that will land on a future block + let note = mock_chain + .add_p2id_note( + account.id(), + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(), + &[], + NoteType::Private, + None, + ) + .unwrap(); + mock_chain.seal_next_block(); + + // Get as input note, and assert that the note was created after block 1 (which we'll + // use as reference) + let input_note = mock_chain + .available_notes() + .iter() + .find(|n| n.id() == note.id()) + .unwrap() + .clone(); + assert!(input_note.location().unwrap().block_num() > 1.into()); + + mock_chain.seal_next_block(); + + // Attempt to execute with a note created in the future + let tx_context = mock_chain.build_tx_context(account.id(), &[], &[]).build(); + + let tx_executor = TransactionExecutor::new(Arc::new(tx_context), None); + // Try to execute with block_ref==1 + let error = tx_executor.execute_transaction( + account.id(), + BlockNumber::from(1), + InputNotes::new(vec![input_note]).unwrap(), + TransactionArgs::default(), + ); + + assert_matches::assert_matches!( + error, + Err(TransactionExecutorError::NoteBlockPastReferenceBlock(..)) + ); +} + #[test] fn test_create_note() { let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); @@ -67,7 +151,12 @@ fn test_create_note() { tag = tag, ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); assert_eq!( read_root_mem_word(&process.into(), NUM_OUTPUT_NOTES_PTR), @@ -123,9 +212,23 @@ fn test_create_note_with_invalid_tag() { let valid_tag: Felt = NoteTag::for_local_use_case(0, 0).unwrap().into(); // Test invalid tag - assert!(tx_context.execute_code(¬e_creation_script(invalid_tag)).is_err()); + assert!( + tx_context + .execute_code_with_assembler( + ¬e_creation_script(invalid_tag), + TransactionKernel::testing_assembler() + ) + .is_err() + ); // Test valid tag - assert!(tx_context.execute_code(¬e_creation_script(valid_tag)).is_ok()); + assert!( + tx_context + .execute_code_with_assembler( + ¬e_creation_script(valid_tag), + TransactionKernel::testing_assembler() + ) + .is_ok() + ); fn note_creation_script(tag: Felt) -> String { format!( @@ -188,7 +291,10 @@ fn test_create_note_too_many_notes() { aux = Felt::ZERO, ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT); } @@ -318,7 +424,12 @@ fn test_get_output_notes_commitment() { )), ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); assert_eq!( @@ -395,7 +506,12 @@ fn test_create_note_and_add_asset() { asset = word_to_masm_push_string(&asset), ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); assert_eq!( @@ -477,7 +593,12 @@ fn test_create_note_and_add_multiple_assets() { nft = word_to_masm_push_string(&non_fungible_asset_encoded), ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); let process_state: ProcessState = process.into(); assert_eq!( @@ -560,7 +681,10 @@ fn test_create_note_and_add_same_nft_twice() { nft = word_to_masm_push_string(&encoded), ); - let process = tx_context.execute_code(&code); + let process = tx_context.execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ); assert_execution_error!(process, ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS); } @@ -630,7 +754,12 @@ fn test_build_recipient_hash() { aux = aux, ); - let process = &tx_context.execute_code(&code).unwrap(); + let process = &tx_context + .execute_code_with_assembler( + &code, + TransactionKernel::testing_assembler_with_mock_account(), + ) + .unwrap(); assert_eq!( read_root_mem_word(&process.into(), NUM_OUTPUT_NOTES_PTR), @@ -675,7 +804,9 @@ fn test_block_procedures() { end "; - let process = &tx_context.execute_code(code).unwrap(); + let process = &tx_context + .execute_code_with_assembler(code, TransactionKernel::testing_assembler_with_mock_account()) + .unwrap(); assert_eq!( process.stack.get_word(0), diff --git a/crates/miden-tx/src/tests/mod.rs b/crates/miden-tx/src/tests/mod.rs index 6d6cd40bb7..3d727110ca 100644 --- a/crates/miden-tx/src/tests/mod.rs +++ b/crates/miden-tx/src/tests/mod.rs @@ -9,10 +9,10 @@ use ::assembly::{ LibraryPath, ast::{Module, ModuleKind}, }; -use miden_lib::transaction::TransactionKernel; +use miden_lib::{transaction::TransactionKernel, utils::word_to_masm_push_string}; use miden_objects::{ - Felt, MIN_PROOF_SECURITY_LEVEL, Word, - account::{AccountBuilder, AccountComponent, AccountStorage, StorageSlot}, + Felt, FieldElement, MIN_PROOF_SECURITY_LEVEL, Word, + account::{Account, AccountBuilder, AccountComponent, AccountStorage, StorageSlot}, assembly::DefaultSourceManager, asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset}, note::{ @@ -24,13 +24,13 @@ use miden_objects::{ account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, }, constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}, note::DEFAULT_NOTE_CODE, storage::{STORAGE_INDEX_0, STORAGE_INDEX_2}, }, - transaction::{ProvenTransaction, TransactionArgs, TransactionScript}, - utils::word_to_masm_push_string, + transaction::{OutputNote, ProvenTransaction, TransactionScript}, }; use miden_prover::ProvingOptions; use rand::{Rng, SeedableRng}; @@ -57,21 +57,7 @@ fn transaction_executor_witness() { .with_mock_notes_preserved() .build(); - let executor = TransactionExecutor::new(tx_context.get_data_store(), None); - - let account_id = tx_context.account().id(); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); - - let executed_transaction = executor - .execute_transaction(account_id, block_ref, ¬e_ids, tx_context.tx_args().clone()) - .unwrap(); + let executed_transaction = tx_context.execute().unwrap(); let tx_inputs = executed_transaction.tx_inputs(); let tx_args = executed_transaction.tx_args(); @@ -81,12 +67,13 @@ fn transaction_executor_witness() { tx_inputs, tx_args, Some(executed_transaction.advice_witness().clone()), - ); + ) + .unwrap(); let mem_advice_provider: MemAdviceProvider = advice_inputs.into(); // load account/note/tx_script MAST to the mast_store let mast_store = Arc::new(TransactionMastStore::new()); - mast_store.load_transaction_code(tx_inputs, tx_args); + mast_store.load_transaction_code(tx_inputs.account().code(), tx_inputs.input_notes(), tx_args); let mut host: TransactionHost = TransactionHost::new( tx_inputs.account().into(), @@ -135,10 +122,6 @@ fn executed_transaction_account_delta_new() { .build_existing() .unwrap(); - let mut tx_context = TransactionContextBuilder::new(account) - .with_mock_notes_preserved_with_account_vault_delta() - .build(); - // updated storage let updated_slot_value = [Felt::new(7), Felt::new(9), Felt::new(11), Felt::new(13)]; @@ -189,8 +172,8 @@ fn executed_transaction_account_delta_new() { " ### note {i} # prepare the stack for a new note creation - push.0.1.2.3 # recipient - push.{EXECUTION_HINT} # note_execution_hint + push.0.1.2.3 # recipient + push.{EXECUTION_HINT} # note_execution_hint push.{NOTETYPE} # note_type push.{aux} # aux push.{tag} # tag @@ -211,7 +194,6 @@ fn executed_transaction_account_delta_new() { # clear the stack dropw dropw dropw dropw - ", EXECUTION_HINT = hints[i], NOTETYPE = note_types[i] as u8, @@ -230,7 +212,7 @@ fn executed_transaction_account_delta_new() { begin ## Update account storage item ## ------------------------------------------------------------------------------------ - # push a new value for the storage slot onto the stack + # push a new value for the storage slot onto the stack push.{UPDATED_SLOT_VALUE} # => [13, 11, 9, 7] @@ -243,7 +225,7 @@ fn executed_transaction_account_delta_new() { ## Update account storage map ## ------------------------------------------------------------------------------------ - # push a new VALUE for the storage map onto the stack + # push a new VALUE for the storage map onto the stack push.{UPDATED_MAP_VALUE} # => [18, 19, 20, 21] @@ -265,7 +247,7 @@ fn executed_transaction_account_delta_new() { ## Update the account nonce ## ------------------------------------------------------------------------------------ - push.1 call.account::incr_nonce drop + push.1 call.account::incr_nonce drop # => [] end ", @@ -281,18 +263,26 @@ fn executed_transaction_account_delta_new() { ) .unwrap(); - let tx_args = TransactionArgs::new( - Some(tx_script), - None, - tx_context.tx_args().advice_inputs().clone().map, - ); + let tx_context = TransactionContextBuilder::new(account) + .with_mock_notes_preserved_with_account_vault_delta() + .tx_script(tx_script) + .build(); - tx_context.set_tx_args(tx_args); + // Storing assets that will be added to assert correctness later + let added_assets = tx_context + .tx_inputs() + .input_notes() + .iter() + .find_map(|n| { + let assets = n.note().assets(); + (assets.num_assets() == 3).then(|| assets.iter().cloned().collect::>()) + }) + .unwrap(); // expected delta // -------------------------------------------------------------------------------------------- // execute the transaction and get the witness - let executed_transaction = tx_context.clone().execute().unwrap(); + let executed_transaction = tx_context.execute().unwrap(); // nonce delta // -------------------------------------------------------------------------------------------- @@ -324,18 +314,6 @@ fn executed_transaction_account_delta_new() { // vault delta // -------------------------------------------------------------------------------------------- // assert that added assets are tracked - let added_assets = tx_context - .tx_inputs() - .input_notes() - .iter() - .find(|n| n.note().assets().num_assets() == 3) - .unwrap() - .note() - .assets() - .iter() - .cloned() - .collect::>(); - assert!( executed_transaction .account_delta() @@ -364,11 +342,6 @@ fn executed_transaction_account_delta_new() { #[test] fn test_empty_delta_nonce_update() { - let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); - - let executor = TransactionExecutor::new(tx_context.get_data_store(), None); - let account_id = tx_context.tx_inputs().account().id(); - let tx_script_src = " use.test::account begin @@ -388,25 +361,15 @@ fn test_empty_delta_nonce_update() { TransactionKernel::testing_assembler_with_mock_account(), ) .unwrap(); - let tx_args = TransactionArgs::new( - Some(tx_script), - None, - tx_context.tx_args().advice_inputs().clone().map, - ); - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .tx_script(tx_script) + .build(); // expected delta // -------------------------------------------------------------------------------------------- // execute the transaction and get the witness - let executed_transaction = - executor.execute_transaction(account_id, block_ref, ¬e_ids, tx_args).unwrap(); + let executed_transaction = tx_context.execute().unwrap(); // nonce delta // -------------------------------------------------------------------------------------------- @@ -422,13 +385,6 @@ fn test_empty_delta_nonce_update() { #[test] fn test_send_note_proc() { - let tx_context = TransactionContextBuilder::with_standard_account(ONE) - .with_mock_notes_preserved_with_account_vault_delta() - .build(); - - let executor = TransactionExecutor::new(tx_context.get_data_store(), None).with_debug_mode(); - let account_id = tx_context.tx_inputs().account().id(); - // removed assets let removed_asset_1 = FungibleAsset::mock(FUNGIBLE_ASSET_AMOUNT / 2); let removed_asset_2 = Asset::Fungible( @@ -524,25 +480,17 @@ fn test_send_note_proc() { TransactionKernel::testing_assembler_with_mock_account(), ) .unwrap(); - let tx_args = TransactionArgs::new( - Some(tx_script), - None, - tx_context.tx_args().advice_inputs().clone().map, - ); - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .tx_script(tx_script) + .with_mock_notes_preserved_with_account_vault_delta() + .build(); // expected delta // -------------------------------------------------------------------------------------------- // execute the transaction and get the witness - let executed_transaction = executor - .execute_transaction(account_id, block_ref, ¬e_ids, tx_args) + let executed_transaction = tx_context + .execute() .unwrap_or_else(|_| panic!("test failed in iteration {idx}")); // nonce delta @@ -568,12 +516,12 @@ fn test_send_note_proc() { #[test] fn executed_transaction_output_notes() { - let tx_context = TransactionContextBuilder::with_standard_account(ONE) - .with_mock_notes_preserved_with_account_vault_delta() - .build(); - - let executor = TransactionExecutor::new(tx_context.get_data_store(), None).with_debug_mode(); - let account_id = tx_context.tx_inputs().account().id(); + let executor_account = Account::mock( + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, + Felt::ONE, + TransactionKernel::testing_assembler(), + ); + let account_id = executor_account.id(); // removed assets let removed_asset_1 = FungibleAsset::mock(FUNGIBLE_ASSET_AMOUNT / 2); @@ -759,29 +707,21 @@ fn executed_transaction_output_notes() { TransactionKernel::testing_assembler_with_mock_account().with_debug_mode(true), ) .unwrap(); - let mut tx_args = TransactionArgs::new( - Some(tx_script), - None, - tx_context.tx_args().advice_inputs().clone().map, - ); - - tx_args.add_output_note_recipient(&expected_output_note_2); - tx_args.add_output_note_recipient(&expected_output_note_3); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); // expected delta // -------------------------------------------------------------------------------------------- // execute the transaction and get the witness - let executed_transaction = - executor.execute_transaction(account_id, block_ref, ¬e_ids, tx_args).unwrap(); + let tx_context = TransactionContextBuilder::new(executor_account) + .with_mock_notes_preserved_with_account_vault_delta() + .tx_script(tx_script) + .expected_notes(vec![ + OutputNote::Full(expected_output_note_2.clone()), + OutputNote::Full(expected_output_note_3.clone()), + ]) + .build(); + + let executed_transaction = tx_context.execute().unwrap(); // output notes // -------------------------------------------------------------------------------------------- @@ -818,17 +758,11 @@ fn prove_witness_and_verify() { let account_id = tx_context.tx_inputs().account().id(); let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); - - let executor = TransactionExecutor::new(tx_context.get_data_store(), None); - let executed_transaction = executor - .execute_transaction(account_id, block_ref, ¬e_ids, tx_context.tx_args().clone()) - .unwrap(); + let notes = tx_context.tx_inputs().input_notes().clone(); + let tx_args = tx_context.tx_args().clone(); + let executor = TransactionExecutor::new(Arc::new(tx_context), None); + let executed_transaction = + executor.execute_transaction(account_id, block_ref, notes, tx_args).unwrap(); let executed_transaction_id = executed_transaction.id(); let proof_options = ProvingOptions::default(); @@ -848,21 +782,6 @@ fn prove_witness_and_verify() { #[test] fn test_tx_script() { - let tx_context = TransactionContextBuilder::with_standard_account(ONE) - .with_mock_notes_preserved() - .build(); - let executor = TransactionExecutor::new(tx_context.get_data_store(), None); - - let account_id = tx_context.tx_inputs().account().id(); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); - let tx_script_input_key = [Felt::new(9999), Felt::new(8888), Felt::new(9999), Felt::new(8888)]; let tx_script_input_value = [Felt::new(9), Felt::new(8), Felt::new(7), Felt::new(6)]; let tx_script_src = format!( @@ -888,14 +807,13 @@ fn test_tx_script() { TransactionKernel::testing_assembler(), ) .unwrap(); - let tx_args = TransactionArgs::new( - Some(tx_script), - None, - tx_context.tx_args().advice_inputs().clone().map, - ); - let executed_transaction = - executor.execute_transaction(account_id, block_ref, ¬e_ids, tx_args); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .tx_script(tx_script) + .build(); + + let executed_transaction = tx_context.execute(); assert!( executed_transaction.is_ok(), @@ -969,8 +887,6 @@ fn transaction_executor_account_code_using_custom_library() { .build_existing() .unwrap(); - let tx_context = TransactionContextBuilder::new(native_account).build(); - let tx_script = TransactionScript::compile( tx_script_src, [], @@ -980,21 +896,12 @@ fn transaction_executor_account_code_using_custom_library() { ) .unwrap(); - let tx_args = TransactionArgs::new( - Some(tx_script), - None, - tx_context.tx_args().advice_inputs().clone().map, - ); - - let mut executor = TransactionExecutor::new(tx_context.get_data_store(), None); - // Load the external library into the executor to make it available during transaction - // execution. - executor.load_library(&external_library); - - let account_id = tx_context.account().id(); - let block_ref = tx_context.tx_inputs().block_header().block_num(); + let tx_context = TransactionContextBuilder::new(native_account.clone()) + .libraries(vec![external_library]) + .tx_script(tx_script) + .build(); - let executed_tx = executor.execute_transaction(account_id, block_ref, &[], tx_args).unwrap(); + let executed_tx = tx_context.execute().unwrap(); // Account's initial nonce of 1 should have been incremented by 4. assert_eq!(executed_tx.account_delta().nonce().unwrap(), Felt::new(5)); @@ -1034,15 +941,17 @@ fn test_execute_program() { let tx_script = TransactionScript::compile(source, [], assembler) .expect("failed to compile the source script"); - let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .tx_script(tx_script.clone()) + .build(); let account_id = tx_context.account().id(); let block_ref = tx_context.tx_inputs().block_header().block_num(); - let advice_inputs = tx_context.tx_args().advice_inputs(); + let advice_inputs = tx_context.tx_args().advice_inputs().clone(); - let executor = TransactionExecutor::new(tx_context.get_data_store(), None); + let executor = TransactionExecutor::new(Arc::new(tx_context), None); let stack_outputs = executor - .execute_tx_view_script(account_id, block_ref, tx_script, advice_inputs.clone()) + .execute_tx_view_script(account_id, block_ref, tx_script, advice_inputs, Vec::default()) .unwrap(); assert_eq!(stack_outputs[..3], [Felt::new(7), Felt::new(2), ONE]); diff --git a/crates/miden-tx/tests/integration/scripts/faucet.rs b/crates/miden-tx/tests/integration/scripts/faucet.rs index 620b9a06d0..943655503f 100644 --- a/crates/miden-tx/tests/integration/scripts/faucet.rs +++ b/crates/miden-tx/tests/integration/scripts/faucet.rs @@ -9,9 +9,11 @@ use miden_objects::{ asset::{Asset, FungibleAsset}, note::{NoteAssets, NoteExecutionHint, NoteId, NoteMetadata, NoteTag, NoteType}, transaction::TransactionScript, +}; +use miden_tx::{ + testing::{Auth, MockChain}, utils::word_to_masm_push_string, }; -use miden_tx::testing::{Auth, MockChain}; use crate::{ assert_transaction_executor_error, get_note_with_fungible_asset_and_script, diff --git a/crates/miden-tx/tests/integration/scripts/p2id.rs b/crates/miden-tx/tests/integration/scripts/p2id.rs index b75ee7a6a6..17a4bfc901 100644 --- a/crates/miden-tx/tests/integration/scripts/p2id.rs +++ b/crates/miden-tx/tests/integration/scripts/p2id.rs @@ -14,9 +14,11 @@ use miden_objects::{ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2, ACCOUNT_ID_SENDER, }, transaction::{OutputNote, TransactionScript}, +}; +use miden_tx::{ + testing::{Auth, MockChain}, utils::word_to_masm_push_string, }; -use miden_tx::testing::{Auth, MockChain}; use crate::{assert_transaction_executor_error, prove_and_verify_transaction}; diff --git a/crates/miden-tx/tests/integration/scripts/swap.rs b/crates/miden-tx/tests/integration/scripts/swap.rs index f0785fade3..1561af14b1 100644 --- a/crates/miden-tx/tests/integration/scripts/swap.rs +++ b/crates/miden-tx/tests/integration/scripts/swap.rs @@ -6,9 +6,11 @@ use miden_objects::{ crypto::rand::RpoRandomCoin, note::{Note, NoteDetails, NoteType}, transaction::{OutputNote, TransactionScript}, +}; +use miden_tx::{ + testing::{Auth, MockChain}, utils::word_to_masm_push_string, }; -use miden_tx::testing::{Auth, MockChain}; use crate::prove_and_verify_transaction;