diff --git a/CHANGELOG.md b/CHANGELOG.md index f4691fbe7..8b627b2cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Changes +- Skip requests to the `DataStore` for asset vault witnesses which are already in transaction inputs ([#2298](https://github.com/0xMiden/miden-base/pull/2298)). - [BREAKING] refactored `TransactionAuthenticator::get_public_key()` method to return `Arc `instead of `&PublicKey` ([#2304](https://github.com/0xMiden/miden-base/pull/2304)). ## 0.13.0 (2026-01-16) diff --git a/crates/miden-protocol/src/asset/vault/vault_key.rs b/crates/miden-protocol/src/asset/vault/vault_key.rs index 2cff63d04..1d3d2a691 100644 --- a/crates/miden-protocol/src/asset/vault/vault_key.rs +++ b/crates/miden-protocol/src/asset/vault/vault_key.rs @@ -82,6 +82,11 @@ impl AssetVaultKey { } } + /// Returns a reference to the inner [Word] of this key. + pub fn as_word(&self) -> &Word { + &self.0 + } + /// Returns `true` if the asset key is for a fungible asset, `false` otherwise. fn is_fungible(&self) -> bool { self.0[0].as_int() == 0 && self.0[1].as_int() == 0 diff --git a/crates/miden-protocol/src/errors/mod.rs b/crates/miden-protocol/src/errors/mod.rs index 19236a647..63ddcf34a 100644 --- a/crates/miden-protocol/src/errors/mod.rs +++ b/crates/miden-protocol/src/errors/mod.rs @@ -734,7 +734,7 @@ pub enum TransactionInputsExtractionError { MissingMapRoot, #[error("failed to construct SMT proof")] SmtProofError(#[from] SmtProofError), - #[error("failed to construct asset witness")] + #[error("failed to construct an asset")] AssetError(#[from] AssetError), #[error("failed to handle storage map data")] StorageMapError(#[from] StorageMapError), diff --git a/crates/miden-protocol/src/transaction/inputs/mod.rs b/crates/miden-protocol/src/transaction/inputs/mod.rs index a8122e090..1e879e660 100644 --- a/crates/miden-protocol/src/transaction/inputs/mod.rs +++ b/crates/miden-protocol/src/transaction/inputs/mod.rs @@ -3,8 +3,8 @@ use alloc::vec::Vec; use core::fmt::Debug; use miden_core::utils::{Deserializable, Serializable}; -use miden_crypto::merkle::NodeIndex; use miden_crypto::merkle::smt::{LeafIndex, SmtLeaf, SmtProof}; +use miden_crypto::merkle::{MerkleError, NodeIndex}; use super::PartialBlockchain; use crate::account::{ @@ -19,7 +19,7 @@ use crate::account::{ StorageSlotId, StorageSlotName, }; -use crate::asset::{AssetVaultKey, AssetWitness, PartialVault}; +use crate::asset::{Asset, AssetVaultKey, AssetWitness, PartialVault}; use crate::block::account_tree::{AccountWitness, account_id_to_smt_index}; use crate::block::{BlockHeader, BlockNumber}; use crate::crypto::merkle::SparseMerklePath; @@ -51,8 +51,6 @@ pub struct TransactionInputs { tx_args: TransactionArgs, advice_inputs: AdviceInputs, foreign_account_code: Vec, - /// Pre-fetched asset witnesses for note assets and the fee asset. - asset_witnesses: Vec, /// Storage slot names for foreign accounts. foreign_account_slot_names: BTreeMap, } @@ -110,14 +108,20 @@ impl TransactionInputs { tx_args: TransactionArgs::default(), advice_inputs: AdviceInputs::default(), foreign_account_code: Vec::new(), - asset_witnesses: Vec::new(), foreign_account_slot_names: BTreeMap::new(), }) } /// Replaces the transaction inputs and assigns the given asset witnesses. pub fn with_asset_witnesses(mut self, witnesses: Vec) -> Self { - self.asset_witnesses = witnesses; + for witness in witnesses { + self.advice_inputs.store.extend(witness.authenticated_nodes()); + let smt_proof = SmtProof::from(witness); + self.advice_inputs + .map + .extend([(smt_proof.leaf().hash(), smt_proof.leaf().to_elements())]); + } + self } @@ -210,11 +214,6 @@ impl TransactionInputs { &self.foreign_account_code } - /// Returns the pre-fetched witnesses for note and fee assets. - pub fn asset_witnesses(&self) -> &[AssetWitness] { - &self.asset_witnesses - } - /// Returns the foreign account storage slot names. pub fn foreign_account_slot_names(&self) -> &BTreeMap { &self.foreign_account_slot_names @@ -263,6 +262,12 @@ impl TransactionInputs { } /// Reads the vault asset witnesses for the given account and vault keys. + /// + /// # Errors + /// Returns an error if: + /// - A Merkle tree with the specified root is not present in the advice data of these inputs. + /// - Witnesses for any of the requested assets are not in the specified Merkle tree. + /// - Construction of the Merkle path or the leaf node for the witness fails. pub fn read_vault_asset_witnesses( &self, vault_root: Word, @@ -292,6 +297,64 @@ impl TransactionInputs { Ok(asset_witnesses) } + /// Returns true if the witness for the specified asset key is present in these inputs. + /// + /// Note that this does not verify the witness' validity (i.e., that the witness is for a valid + /// asset). + pub fn has_vault_asset_witness(&self, vault_root: Word, asset_key: &AssetVaultKey) -> bool { + let smt_index: NodeIndex = asset_key.to_leaf_index().into(); + + // make sure the path is in the Merkle store + if !self.advice_inputs.store.has_path(vault_root, smt_index) { + return false; + } + + // make sure the node pre-image is in the Merkle store + match self.advice_inputs.store.get_node(vault_root, smt_index) { + Ok(node) => self.advice_inputs.map.contains_key(&node), + Err(_) => false, + } + } + + /// Reads the asset from the specified vault under the specified key; returns `None` if the + /// specified asset is not present in these inputs. + /// + /// # Errors + /// Returns an error if: + /// - A Merkle tree with the specified root is not present in the advice data of these inputs. + /// - Construction of the leaf node or the asset fails. + pub fn read_vault_asset( + &self, + vault_root: Word, + asset_key: AssetVaultKey, + ) -> Result, TransactionInputsExtractionError> { + // Get the node corresponding to the asset_key; if not found return None + let smt_index = asset_key.to_leaf_index(); + let merkle_node = match self.advice_inputs.store.get_node(vault_root, smt_index.into()) { + Ok(node) => node, + Err(MerkleError::NodeIndexNotFoundInStore(..)) => return Ok(None), + Err(err) => return Err(err.into()), + }; + + // Construct SMT leaf for this asset key + let smt_leaf_elements = self + .advice_inputs + .map + .get(&merkle_node) + .ok_or(TransactionInputsExtractionError::MissingVaultRoot)?; + let smt_leaf = smt_leaf_from_elements(smt_leaf_elements, smt_index)?; + + // Find the asset in the SMT leaf + let asset = smt_leaf + .entries() + .iter() + .find(|(key, _value)| key == asset_key.as_word()) + .map(|(_key, value)| Asset::try_from(value)) + .transpose()?; + + Ok(asset) + } + /// Reads AccountInputs for a foreign account from the advice inputs. /// /// This function reverses the process of [`TransactionAdviceInputs::add_foreign_accounts`] by: @@ -432,7 +495,6 @@ impl Serializable for TransactionInputs { self.tx_args.write_into(target); self.advice_inputs.write_into(target); self.foreign_account_code.write_into(target); - self.asset_witnesses.write_into(target); self.foreign_account_slot_names.write_into(target); } } @@ -448,7 +510,6 @@ impl Deserializable for TransactionInputs { let tx_args = TransactionArgs::read_from(source)?; let advice_inputs = AdviceInputs::read_from(source)?; let foreign_account_code = Vec::::read_from(source)?; - let asset_witnesses = Vec::::read_from(source)?; let foreign_account_slot_names = BTreeMap::::read_from(source)?; @@ -460,7 +521,6 @@ impl Deserializable for TransactionInputs { tx_args, advice_inputs, foreign_account_code, - asset_witnesses, foreign_account_slot_names, }) } diff --git a/crates/miden-protocol/src/transaction/inputs/tests.rs b/crates/miden-protocol/src/transaction/inputs/tests.rs index 09500ef25..1d5547de7 100644 --- a/crates/miden-protocol/src/transaction/inputs/tests.rs +++ b/crates/miden-protocol/src/transaction/inputs/tests.rs @@ -55,7 +55,6 @@ fn test_read_foreign_account_inputs_missing_data() { tx_args: crate::transaction::TransactionArgs::default(), advice_inputs: crate::vm::AdviceInputs::default(), foreign_account_code: Vec::new(), - asset_witnesses: Vec::new(), foreign_account_slot_names: BTreeMap::new(), }; @@ -139,7 +138,6 @@ fn test_read_foreign_account_inputs_with_storage_data() { tx_args: crate::transaction::TransactionArgs::default(), advice_inputs, foreign_account_code: vec![code], - asset_witnesses: Vec::new(), foreign_account_slot_names, }; @@ -261,7 +259,6 @@ fn test_read_foreign_account_inputs_with_proper_witness() { tx_args: crate::transaction::TransactionArgs::default(), advice_inputs, foreign_account_code: vec![code], - asset_witnesses: Vec::new(), foreign_account_slot_names: BTreeMap::new(), }; @@ -348,7 +345,6 @@ fn test_transaction_inputs_serialization_with_foreign_slot_names() { tx_args: crate::transaction::TransactionArgs::default(), advice_inputs: crate::vm::AdviceInputs::default(), foreign_account_code: Vec::new(), - asset_witnesses: Vec::new(), foreign_account_slot_names, }; diff --git a/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs b/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs index aec2c0422..a6b5311a5 100644 --- a/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs +++ b/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs @@ -3,11 +3,9 @@ use alloc::vec::Vec; use miden_processor::AdviceMutation; use crate::account::{AccountHeader, AccountId, PartialAccount}; -use crate::asset::AssetWitness; use crate::block::account_tree::AccountWitness; use crate::crypto::SequentialCommit; use crate::crypto::merkle::InnerNodeInfo; -use crate::crypto::merkle::smt::SmtProof; use crate::note::NoteAttachmentContent; use crate::transaction::{ AccountInputs, @@ -75,10 +73,6 @@ impl TransactionAdviceInputs { } } - tx_inputs.asset_witnesses().iter().for_each(|asset_witness| { - inputs.add_asset_witness(asset_witness.clone()); - }); - // Extend with extra user-supplied advice. inputs.extend(tx_inputs.tx_args().advice_inputs().clone()); @@ -309,14 +303,6 @@ impl TransactionAdviceInputs { self.extend_merkle_store(witness.authenticated_nodes()); } - /// Adds an asset witness to the advice inputs. - fn add_asset_witness(&mut self, witness: AssetWitness) { - self.extend_merkle_store(witness.authenticated_nodes()); - - let smt_proof = SmtProof::from(witness); - self.extend_map([(smt_proof.leaf().hash(), smt_proof.leaf().to_elements())]); - } - // NOTE INJECTION // -------------------------------------------------------------------------------------------- diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index 0e0cc1dda..efd053dbc 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -17,6 +17,7 @@ use miden_protocol::errors::{ NoteError, ProvenTransactionError, TransactionInputError, + TransactionInputsExtractionError, TransactionOutputError, }; use miden_protocol::note::{NoteId, NoteMetadata}; @@ -72,6 +73,8 @@ impl From for TransactionExecutorError { #[derive(Debug, Error)] pub enum TransactionExecutorError { + #[error("failed to read fee asset from transaction inputs")] + FeeAssetRetrievalFailed(#[source] TransactionInputsExtractionError), #[error("failed to fetch transaction inputs from the data store")] FetchTransactionInputsFailed(#[source] DataStoreError), #[error("failed to fetch asset witnesses from the data store")] diff --git a/crates/miden-tx/src/executor/mod.rs b/crates/miden-tx/src/executor/mod.rs index af7b87678..4b45fbc16 100644 --- a/crates/miden-tx/src/executor/mod.rs +++ b/crates/miden-tx/src/executor/mod.rs @@ -263,24 +263,34 @@ where .await .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?; - // Add the vault key for the fee asset to the list of asset vault keys which will need to be - // accessed at the end of the transaction. + let native_account_vault_root = account.vault().root(); let fee_asset_vault_key = AssetVaultKey::from_account_id(block_header.fee_parameters().native_asset_id()) .expect("fee asset should be a fungible asset"); + + let mut tx_inputs = TransactionInputs::new(account, block_header, blockchain, input_notes) + .map_err(TransactionExecutorError::InvalidTransactionInputs)? + .with_tx_args(tx_args); + + // Add the vault key for the fee asset to the list of asset vault keys which will need to be + // accessed at the end of the transaction. asset_vault_keys.insert(fee_asset_vault_key); - // Fetch the witnesses for all asset vault keys. - let asset_witnesses = self - .data_store - .get_vault_asset_witnesses(account_id, account.vault().root(), asset_vault_keys) - .await - .map_err(TransactionExecutorError::FetchAssetWitnessFailed)?; + // filter out any asset vault keys for which we already have witnesses in the advice inputs + asset_vault_keys.retain(|asset_key| { + !tx_inputs.has_vault_asset_witness(native_account_vault_root, asset_key) + }); - let tx_inputs = TransactionInputs::new(account, block_header, blockchain, input_notes) - .map_err(TransactionExecutorError::InvalidTransactionInputs)? - .with_tx_args(tx_args) - .with_asset_witnesses(asset_witnesses); + // if any of the witnesses are missing, fetch them from the data store and add to tx_inputs + if !asset_vault_keys.is_empty() { + let asset_witnesses = self + .data_store + .get_vault_asset_witnesses(account_id, native_account_vault_root, asset_vault_keys) + .await + .map_err(TransactionExecutorError::FetchAssetWitnessFailed)?; + + tx_inputs = tx_inputs.with_asset_witnesses(asset_witnesses); + } Ok(tx_inputs) } @@ -318,25 +328,23 @@ where AccountProcedureIndexMap::new([tx_inputs.account().code()]); let initial_fee_asset_balance = { + let vault_root = tx_inputs.account().vault().root(); let native_asset_id = tx_inputs.block_header().fee_parameters().native_asset_id(); let fee_asset_vault_key = AssetVaultKey::from_account_id(native_asset_id) .expect("fee asset should be a fungible asset"); - let fee_asset_witness = tx_inputs - .asset_witnesses() - .iter() - .find_map(|witness| witness.find(fee_asset_vault_key)); - - match fee_asset_witness { + let fee_asset = tx_inputs + .read_vault_asset(vault_root, fee_asset_vault_key) + .map_err(TransactionExecutorError::FeeAssetRetrievalFailed)?; + match fee_asset { Some(Asset::Fungible(fee_asset)) => fee_asset.amount(), Some(Asset::NonFungible(_)) => { return Err(TransactionExecutorError::FeeAssetMustBeFungible); }, - // If the witness does not contain the asset, its balance is zero. + // If the asset was not found, its balance is zero. None => 0, } }; - let host = TransactionExecutorHost::new( tx_inputs.account(), input_notes.clone(),