From 4f4dfa3d715b882e6d90c0bffff06e44df139627 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 11 Sep 2026 14:46:19 +0200 Subject: [PATCH 1/5] feat: defer precompile proofs in local tx prover --- .../src/decoded/transaction/batch/tests.rs | 5 +- .../src/batch/proposed_batch.rs | 69 +++++-------------- crates/miden-protocol/src/errors/mod.rs | 3 - crates/miden-testing/tests/lib.rs | 61 ++++++++++++++-- crates/miden-testing/tests/scripts/faucet.rs | 4 +- .../tests/scripts/fee_sponsorship.rs | 6 +- crates/miden-testing/tests/scripts/p2id.rs | 8 ++- crates/miden-testing/tests/scripts/swap.rs | 8 +-- crates/miden-tx/src/errors/mod.rs | 3 + crates/miden-tx/src/prover/mod.rs | 23 +++++-- 10 files changed, 109 insertions(+), 81 deletions(-) diff --git a/crates/miden-objects/src/decoded/transaction/batch/tests.rs b/crates/miden-objects/src/decoded/transaction/batch/tests.rs index 012da45db9..367768dd1a 100644 --- a/crates/miden-objects/src/decoded/transaction/batch/tests.rs +++ b/crates/miden-objects/src/decoded/transaction/batch/tests.rs @@ -88,10 +88,7 @@ fn proposal_proofs_are_only_checked_by_explicit_verification() { assert!( matches!( error_source::(&error), - Some( - miden_protocol::errors::ProposedBatchError::TransactionVerificationFailed { .. } - | miden_protocol::errors::ProposedBatchError::IncompleteTransactionProof { .. } - ) + Some(miden_protocol::errors::ProposedBatchError::TransactionVerificationFailed { .. }) ), "{error}" ); diff --git a/crates/miden-protocol/src/batch/proposed_batch.rs b/crates/miden-protocol/src/batch/proposed_batch.rs index d4d9d29782..fc413411e2 100644 --- a/crates/miden-protocol/src/batch/proposed_batch.rs +++ b/crates/miden-protocol/src/batch/proposed_batch.rs @@ -341,11 +341,14 @@ impl ProposedBatch { /// Creates a new [`ProposedBatch`] from the provided parts, verifying every transaction's /// execution proof against the transaction kernel. /// + /// Transactions whose precompile claims are still outstanding are accepted: verification checks + /// that their deferred witness matches their VM proof, and the batch prover settles the claims + /// of all transactions in the batch with a single precompile proof. + /// /// # Errors /// /// Returns an error for any of the batch-validation conditions documented on `new_batch_inner`, - /// if a transaction's proof fails to verify or does not meet `proof_security_level`, or if the - /// proof has an outstanding precompile obligation. + /// or if a transaction's proof fails to verify or does not meet `proof_security_level`. pub fn new( transactions: Vec>, reference_block_header: BlockHeader, @@ -362,17 +365,14 @@ impl ProposedBatch { let verifier = TransactionVerifier::new(proof_security_level); for tx in batch.transactions() { - let verification_outcome = verifier.verify(tx).map_err(|source| { + // The outcome may carry an outstanding precompile obligation, which the batch prover + // settles for all transactions at once. + let _verification_outcome = verifier.verify(tx).map_err(|source| { ProposedBatchError::TransactionVerificationFailed { transaction_id: tx.id(), source, } })?; - if !verification_outcome.is_complete() { - return Err(ProposedBatchError::IncompleteTransactionProof { - transaction_id: tx.id(), - }); - } } Ok(batch) @@ -511,13 +511,6 @@ impl Deserializable for ProposedBatch { .map(Arc::new) .collect::>>(); - if let Some(tx) = transactions.iter().find(|tx| !tx.proof().is_complete()) { - return Err(DeserializationError::InvalidValue(format!( - "transaction {} has an outstanding precompile obligation", - tx.id() - ))); - } - let block_header = BlockHeader::read_from(source)?; let partial_blockchain = PartialBlockchain::read_from(source)?; let unauthenticated_note_proofs = @@ -546,9 +539,14 @@ mod tests { use crate::Word; use crate::account::{AccountType, AccountUpdateDetails}; use crate::transaction::{InputNoteCommitment, OutputNote, ProvenTransaction, TxAccountUpdate}; - - #[test] - fn proposed_batch_serialization() -> anyhow::Result<()> { + use crate::vm::ExecutionProof; + + /// A proposed batch round-trips whether or not its transactions still owe precompile work, + /// since settling that work is the batch prover's job. + #[rstest::rstest] + #[case::complete(crate::testing::dummy_execution_proof())] + #[case::deferred(crate::testing::dummy_deferred_execution_proof())] + fn proposed_batch_serialization(#[case] proof: ExecutionProof) -> anyhow::Result<()> { // create partial blockchain with 3 blocks - i.e., 2 peaks let mut mmr = Mmr::default(); for i in 0..3 { @@ -575,7 +573,6 @@ mod tests { let block_num = reference_block_header.block_num(); let block_ref = reference_block_header.commitment(); let expiration_block_num = reference_block_header.block_num() + 1; - let proof = crate::testing::dummy_execution_proof(); let account_update = TxAccountUpdate::new( account_id, @@ -587,7 +584,7 @@ mod tests { .context("failed to build account update")?; let tx = ProvenTransaction::new( - account_update.clone(), + account_update, Vec::::new(), Vec::::new(), block_num, @@ -599,8 +596,8 @@ mod tests { let batch = ProposedBatch::new_unverified( vec![Arc::new(tx)], - reference_block_header.clone(), - partial_blockchain.clone(), + reference_block_header, + partial_blockchain, BTreeMap::new(), ) .context("failed to propose batch")?; @@ -620,34 +617,6 @@ mod tests { assert_eq!(batch.input_notes, batch2.input_notes); assert_eq!(batch.output_notes, batch2.output_notes); - let tx = ProvenTransaction::new( - account_update, - Vec::::new(), - Vec::::new(), - block_num, - block_ref, - expiration_block_num, - crate::testing::dummy_deferred_execution_proof(), - ) - .context("failed to build deferred proven transaction")?; - let transaction_id = tx.id(); - let batch = ProposedBatch::new_unverified( - vec![Arc::new(tx)], - reference_block_header, - partial_blockchain, - BTreeMap::new(), - ) - .context("failed to propose deferred batch")?; - - let error = ProposedBatch::read_from_bytes(&batch.to_bytes()).unwrap_err(); - let expected_error = - format!("transaction {transaction_id} has an outstanding precompile obligation"); - assert_matches::assert_matches!( - error, - DeserializationError::InvalidValue(message) - if message == expected_error - ); - Ok(()) } } diff --git a/crates/miden-protocol/src/errors/mod.rs b/crates/miden-protocol/src/errors/mod.rs index 384f0d4daf..ac124b1470 100644 --- a/crates/miden-protocol/src/errors/mod.rs +++ b/crates/miden-protocol/src/errors/mod.rs @@ -1351,9 +1351,6 @@ pub enum ProposedBatchError { source: TransactionVerifierError, }, - #[error("transaction {transaction_id} has an outstanding precompile obligation")] - IncompleteTransactionProof { transaction_id: TransactionId }, - #[error( "transaction batch has {0} input notes but at most {MAX_INPUT_NOTES_PER_BATCH} are allowed" )] diff --git a/crates/miden-testing/tests/lib.rs b/crates/miden-testing/tests/lib.rs index 1866507c67..c6739a11de 100644 --- a/crates/miden-testing/tests/lib.rs +++ b/crates/miden-testing/tests/lib.rs @@ -22,17 +22,40 @@ use miden_protocol::note::{ use miden_protocol::testing::account_id::ACCOUNT_ID_SENDER; use miden_protocol::transaction::{ExecutedTransaction, ProvenTransaction, TransactionVerifier}; use miden_protocol::utils::serde::Deserializable; +#[cfg(test)] +use miden_protocol::vm::VerificationOutcome; use miden_standards::code_builder::CodeBuilder; use miden_testing::{Auth, MockChain}; use miden_tx::{LocalTransactionProver, Prover}; +use rstest::rstest; // HELPER FUNCTIONS // ================================================================================================ #[cfg(test)] -pub async fn prove_and_verify_transaction( +pub async fn prove_and_verify_transaction_deferred( + executed_transaction: ExecutedTransaction, +) -> Result<(), TransactionVerifierError> { + let outcome = prove_and_verify_transaction(executed_transaction).await?; + assert!(!outcome.is_complete()); + Ok(()) +} + +/// Proves `executed_transaction` locally, round-trips it and verifies it. +#[cfg(test)] +pub async fn prove_and_verify_transaction_complete( executed_transaction: ExecutedTransaction, ) -> Result<(), TransactionVerifierError> { + let outcome = prove_and_verify_transaction(executed_transaction).await?; + assert!(outcome.is_complete()); + Ok(()) +} + +/// Proves `executed_transaction` locally, round-trips it and verifies it. +#[cfg(test)] +pub async fn prove_and_verify_transaction( + executed_transaction: ExecutedTransaction, +) -> Result { use miden_protocol::transaction::TransactionHeader; let executed_transaction_id = executed_transaction.id(); @@ -54,10 +77,38 @@ pub async fn prove_and_verify_transaction( let verifier = TransactionVerifier::new(miden_protocol::MIN_PROOF_SECURITY_LEVEL); let outcome = verifier.verify(&proven_transaction)?; - assert!( - outcome.is_complete(), - "the local transaction prover must settle precompile work" - ); + + Ok(outcome) +} + +/// The local prover leaves precompile claims for the batch prover, so a transaction that +/// authenticates with ECDSA verifies while its precompile obligation is still outstanding. Falcon +/// is the control: it verifies in-circuit and uses no precompile, so its proof is complete. +#[rstest] +#[case::ecdsa(Auth::basic_ecdsa(), false)] +#[case::falcon(Auth::basic_falcon(), true)] +#[tokio::test] +async fn prove_and_verify_defers_precompile_claims( + #[case] auth: Auth, + #[case] is_complete: bool, +) -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + let account = builder.add_existing_wallet(auth)?; + let note = builder.add_p2any_note(account.id(), NoteType::Public, [])?; + let mock_chain = builder.build()?; + + let executed = mock_chain + .build_transaction(account.id()) + .authenticated_input_note(note.id()) + .build()? + .execute() + .await?; + + if is_complete { + prove_and_verify_transaction_complete(executed).await?; + } else { + prove_and_verify_transaction_deferred(executed).await?; + } Ok(()) } diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index d2fbdbaa17..43db260897 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -70,7 +70,7 @@ use miden_testing::{ }; use rand::RngExt; -use crate::{get_note_with_fungible_asset_and_script, prove_and_verify_transaction}; +use crate::{get_note_with_fungible_asset_and_script, prove_and_verify_transaction_complete}; // Shared test utilities for faucet tests // ================================================================================================ @@ -666,7 +666,7 @@ async fn prove_burning_fungible_asset_on_existing_faucet_succeeds() -> anyhow::R assert_eq!(executed_transaction.input_notes().get_note(0).id(), note.id()); // Prove, serialize/deserialize and verify the transaction - prove_and_verify_transaction(executed_transaction.clone()).await?; + prove_and_verify_transaction_complete(executed_transaction.clone()).await?; Ok(()) } diff --git a/crates/miden-testing/tests/scripts/fee_sponsorship.rs b/crates/miden-testing/tests/scripts/fee_sponsorship.rs index 476dd624cf..3a016f48fc 100644 --- a/crates/miden-testing/tests/scripts/fee_sponsorship.rs +++ b/crates/miden-testing/tests/scripts/fee_sponsorship.rs @@ -78,9 +78,7 @@ fn setup(reclaim_height: Option, reclaimer: Reclaimer) -> anyhow::R let mut rng = RandomCoin::new(Word::empty()); let mut builder = MockChain::builder(); - // The happy-path test proves this account's transaction. Use an auth scheme whose proof does - // not contain settled precompile work. - let network_account = builder.add_existing_wallet(Auth::basic_falcon())?; + let network_account = builder.add_existing_wallet(Auth::basic_ecdsa())?; let sponsor = builder.add_existing_wallet(Auth::basic_ecdsa())?; let stranger = builder.add_existing_wallet(Auth::basic_ecdsa())?; @@ -151,7 +149,7 @@ async fn network_account_consumes_sponsorship_with_feature_note() -> anyhow::Res "the network account should receive the sponsored fee", ); - crate::prove_and_verify_transaction(executed).await?; + crate::prove_and_verify_transaction_deferred(executed).await?; Ok(()) } diff --git a/crates/miden-testing/tests/scripts/p2id.rs b/crates/miden-testing/tests/scripts/p2id.rs index 723c8b0c2e..e8e47d275f 100644 --- a/crates/miden-testing/tests/scripts/p2id.rs +++ b/crates/miden-testing/tests/scripts/p2id.rs @@ -17,7 +17,7 @@ use miden_standards::errors::standards::ERR_P2ID_TARGET_ACCT_MISMATCH; use miden_standards::note::{P2idNote, P2idNoteStorage}; use miden_testing::{Auth, MockChain, assert_transaction_executor_error}; -use crate::prove_and_verify_transaction; +use crate::prove_and_verify_transaction_complete; /// We test the Pay to script with 2 assets to test the loop inside the script. /// So we create a note containing two assets that can only be consumed by the target account. @@ -142,7 +142,7 @@ async fn prove_consume_note_with_new_account() -> anyhow::Result<()> { executed_transaction.final_account().to_commitment(), target_account_after.to_commitment() ); - prove_and_verify_transaction(executed_transaction).await?; + prove_and_verify_transaction_complete(executed_transaction).await?; Ok(()) } @@ -183,7 +183,9 @@ async fn prove_consume_multiple_notes() -> anyhow::Result<()> { let resulting_asset = account.vault().assets().next().unwrap(); assert_eq!(resulting_asset.unwrap_fungible().amount().as_u64(), 123); - Ok(prove_and_verify_transaction(executed_transaction).await?) + prove_and_verify_transaction_complete(executed_transaction).await?; + + Ok(()) } /// Consumes two existing notes and creates two other notes in the same transaction diff --git a/crates/miden-testing/tests/scripts/swap.rs b/crates/miden-testing/tests/scripts/swap.rs index 9ee8675be5..f7775aa14a 100644 --- a/crates/miden-testing/tests/scripts/swap.rs +++ b/crates/miden-testing/tests/scripts/swap.rs @@ -14,7 +14,7 @@ use miden_standards::code_builder::CodeBuilder; use miden_standards::note::P2idNote; use miden_testing::{Auth, MockChain}; -use crate::prove_and_verify_transaction; +use crate::prove_and_verify_transaction_complete; /// Creates a SWAP note from the transaction script and proves and verifies the transaction. #[tokio::test] @@ -83,7 +83,7 @@ pub async fn prove_send_swap_note() -> anyhow::Result<()> { let swap_output_note = create_swap_note_tx.output_notes().iter().next().unwrap(); assert_eq!(swap_output_note.assets().iter().next().unwrap(), &offered_asset); - assert!(prove_and_verify_transaction(create_swap_note_tx).await.is_ok()); + assert!(prove_and_verify_transaction_complete(create_swap_note_tx).await.is_ok()); Ok(()) } @@ -151,11 +151,11 @@ async fn consume_swap_note_private_payback_note() -> anyhow::Result<()> { assert!(sender_account.vault().assets().any(|asset| asset == requested_asset)); - prove_and_verify_transaction(consume_swap_note_tx) + prove_and_verify_transaction_complete(consume_swap_note_tx) .await .context("failed to prove/verify consume_swap_note_tx")?; - prove_and_verify_transaction(consume_payback_tx) + prove_and_verify_transaction_complete(consume_payback_tx) .await .context("failed to prove/verify consume_payback_tx")?; diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index 1f8121b43e..4086690b5e 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -23,6 +23,7 @@ use miden_protocol::errors::{ use miden_protocol::note::{NoteId, PartialNoteMetadata}; use miden_protocol::transaction::{TransactionEventId, TransactionSummary}; use miden_protocol::{Felt, Word}; +use miden_prover::ProverError; use thiserror::Error; // NOTE EXECUTION ERROR @@ -164,6 +165,8 @@ pub enum TransactionProverError { // case, the diagnostic is lost if the execution error is not explicitly unwrapped. #[error("failed to execute transaction kernel program:\n{}", PrintDiagnostic::new(.0))] TransactionProgramExecutionFailed(ExecutionError), + #[error("failed to generate transaction proof")] + TransactionProofGenerationFailed(#[source] ProverError), /// Custom error variant for errors not covered by the other variants. #[error("{error_msg}")] Other { diff --git a/crates/miden-tx/src/prover/mod.rs b/crates/miden-tx/src/prover/mod.rs index 628bd27aac..d9f381888a 100644 --- a/crates/miden-tx/src/prover/mod.rs +++ b/crates/miden-tx/src/prover/mod.rs @@ -1,6 +1,6 @@ use alloc::vec::Vec; -use miden_processor::ExecutionOptions; +use miden_processor::{ExecutionError, ExecutionOptions, FastProcessor}; use miden_protocol::account::{AccountPatch, AccountUpdateDetails, PartialAccount}; use miden_protocol::block::BlockNumber; use miden_protocol::transaction::{ @@ -14,7 +14,7 @@ use miden_protocol::transaction::{ }; use miden_prover::HashFunction::Poseidon2; pub use miden_prover::Prover; -use miden_prover::{ExecutionProof, Word, prove_sync}; +use miden_prover::{ExecutionProof, Word}; use super::TransactionProverError; use crate::host::{AccountProcedureIndexMap, ScriptMastForestStore}; @@ -30,6 +30,9 @@ pub use mast_store::TransactionMastStore; /// Local Transaction prover is a stateless component which is responsible for proving transactions. /// +/// The produced proof covers the VM execution only. Precompile claims are left deferred, because a +/// batch settles the claims of all its transactions with a single precompile proof. +/// /// Each `prove()` call creates a fresh [`TransactionMastStore`] loaded with only the current /// transaction's account code, ensuring no state accumulates across calls. This is important /// in WASM environments where accumulated MAST forests fragment the linear memory. @@ -145,16 +148,24 @@ impl LocalTransactionProver { let advice_inputs = advice_inputs.into_advice_inputs(); - let (stack_outputs, proof) = prove_sync( - &self.prover, - &TransactionKernel::main(), + let processor = FastProcessor::new_with_options( stack_inputs, advice_inputs.clone(), - &mut host, ExecutionOptions::default(), ) + .map_err(ExecutionError::advice_error_no_context) .map_err(TransactionProverError::TransactionProgramExecutionFailed)?; + let witness = processor + .execute_for_proving_sync(&TransactionKernel::main(), &mut host) + .map_err(TransactionProverError::TransactionProgramExecutionFailed)?; + let stack_outputs = *witness.claim().stack_outputs(); + + let proof = self + .prover + .prove(witness) + .map_err(TransactionProverError::TransactionProofGenerationFailed)?; + // Extract transaction outputs and process transaction data. let (account_patch, input_notes, output_notes) = host.into_parts(); let tx_outputs = From c2b5508019ce40cd6961d732285204c2c494201b Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 11 Sep 2026 14:46:38 +0200 Subject: [PATCH 2/5] chore: add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 563279e4d2..08c6c2c780 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Changes - Added type signatures where missing throughout the protocol and standards Miden Assembly libraries +- `LocalTransactionProver` now leaves precompile claims deferred for the batch prover to settle, instead of proving them per transaction ([#3851](https://github.com/0xMiden/protocol/pull/3851)). ## v0.17.0-pre.1 (2026-09-05) From 17366d4cdb02e7cfe747e0762b25d29336ad1689 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 11 Sep 2026 16:37:13 +0200 Subject: [PATCH 3/5] chore: add batch building test --- crates/miden-testing/tests/lib.rs | 40 ++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/crates/miden-testing/tests/lib.rs b/crates/miden-testing/tests/lib.rs index c6739a11de..ac703a1f22 100644 --- a/crates/miden-testing/tests/lib.rs +++ b/crates/miden-testing/tests/lib.rs @@ -6,9 +6,12 @@ mod scripts; mod standards; mod wallet; -use miden_protocol::Word; +use std::iter; +use std::sync::Arc; + use miden_protocol::account::AccountId; use miden_protocol::asset::FungibleAsset; +use miden_protocol::batch::ProposedBatch; use miden_protocol::crypto::utils::Serializable; use miden_protocol::errors::TransactionVerifierError; use miden_protocol::note::{ @@ -24,6 +27,7 @@ use miden_protocol::transaction::{ExecutedTransaction, ProvenTransaction, Transa use miden_protocol::utils::serde::Deserializable; #[cfg(test)] use miden_protocol::vm::VerificationOutcome; +use miden_protocol::{MIN_PROOF_SECURITY_LEVEL, Word}; use miden_standards::code_builder::CodeBuilder; use miden_testing::{Auth, MockChain}; use miden_tx::{LocalTransactionProver, Prover}; @@ -36,7 +40,7 @@ use rstest::rstest; pub async fn prove_and_verify_transaction_deferred( executed_transaction: ExecutedTransaction, ) -> Result<(), TransactionVerifierError> { - let outcome = prove_and_verify_transaction(executed_transaction).await?; + let (_, outcome) = prove_and_verify_transaction(executed_transaction).await?; assert!(!outcome.is_complete()); Ok(()) } @@ -46,16 +50,17 @@ pub async fn prove_and_verify_transaction_deferred( pub async fn prove_and_verify_transaction_complete( executed_transaction: ExecutedTransaction, ) -> Result<(), TransactionVerifierError> { - let outcome = prove_and_verify_transaction(executed_transaction).await?; + let (_, outcome) = prove_and_verify_transaction(executed_transaction).await?; assert!(outcome.is_complete()); Ok(()) } -/// Proves `executed_transaction` locally, round-trips it and verifies it. +/// Proves `executed_transaction` locally, round-trips it and verifies it, returning the proven +/// transaction together with its verification outcome. #[cfg(test)] pub async fn prove_and_verify_transaction( executed_transaction: ExecutedTransaction, -) -> Result { +) -> Result<(ProvenTransaction, VerificationOutcome), TransactionVerifierError> { use miden_protocol::transaction::TransactionHeader; let executed_transaction_id = executed_transaction.id(); @@ -78,12 +83,15 @@ pub async fn prove_and_verify_transaction( let outcome = verifier.verify(&proven_transaction)?; - Ok(outcome) + Ok((proven_transaction, outcome)) } /// The local prover leaves precompile claims for the batch prover, so a transaction that /// authenticates with ECDSA verifies while its precompile obligation is still outstanding. Falcon /// is the control: it verifies in-circuit and uses no precompile, so its proof is complete. +/// +/// Both must also pass `ProposedBatch::new`, which verifies the proof of every transaction it +/// batches. #[rstest] #[case::ecdsa(Auth::basic_ecdsa(), false)] #[case::falcon(Auth::basic_falcon(), true)] @@ -104,11 +112,21 @@ async fn prove_and_verify_defers_precompile_claims( .execute() .await?; - if is_complete { - prove_and_verify_transaction_complete(executed).await?; - } else { - prove_and_verify_transaction_deferred(executed).await?; - } + let (proven_transaction, outcome) = prove_and_verify_transaction(executed).await?; + assert_eq!(outcome.is_complete(), is_complete); + + let transactions = vec![Arc::new(proven_transaction)]; + let (batch_reference_block, partial_blockchain, unauthenticated_note_proofs) = mock_chain + .get_batch_inputs(transactions.iter().map(|tx| tx.ref_block_num()), iter::empty())?; + + let batch = ProposedBatch::new( + transactions, + batch_reference_block, + partial_blockchain, + unauthenticated_note_proofs, + MIN_PROOF_SECURITY_LEVEL, + )?; + assert_eq!(batch.transactions().len(), 1); Ok(()) } From 85c61b2b3dacefbf50b0db203fd670ecb9893782 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Sun, 13 Sep 2026 08:41:43 +0200 Subject: [PATCH 4/5] chore: update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08c6c2c780..2f18dfd70b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ ### Changes - Added type signatures where missing throughout the protocol and standards Miden Assembly libraries + +### Fixes + - `LocalTransactionProver` now leaves precompile claims deferred for the batch prover to settle, instead of proving them per transaction ([#3851](https://github.com/0xMiden/protocol/pull/3851)). ## v0.17.0-pre.1 (2026-09-05) From cfbc7acfb5ace22b11332815ab34ccf47fe87497 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 14 Sep 2026 10:28:15 +0200 Subject: [PATCH 5/5] chore: remove redundant `cfg(test)` --- crates/miden-testing/tests/lib.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/crates/miden-testing/tests/lib.rs b/crates/miden-testing/tests/lib.rs index 35f4a4c2a6..120ab8eb13 100644 --- a/crates/miden-testing/tests/lib.rs +++ b/crates/miden-testing/tests/lib.rs @@ -11,9 +11,6 @@ use std::sync::Arc; use miden_processor::ExecutionError; use miden_processor::advice::AdviceError; -use miden_protocol::MIN_PROOF_SECURITY_LEVEL; -#[cfg(test)] -use miden_protocol::Word; use miden_protocol::account::AccountId; use miden_protocol::asset::FungibleAsset; use miden_protocol::batch::ProposedBatch; @@ -30,8 +27,8 @@ use miden_protocol::note::{ use miden_protocol::testing::account_id::ACCOUNT_ID_SENDER; use miden_protocol::transaction::{ExecutedTransaction, ProvenTransaction, TransactionVerifier}; use miden_protocol::utils::serde::Deserializable; -#[cfg(test)] use miden_protocol::vm::VerificationOutcome; +use miden_protocol::{MIN_PROOF_SECURITY_LEVEL, Word}; use miden_standards::code_builder::CodeBuilder; use miden_testing::{Auth, MockChain}; use miden_tx::{ExecutionOptions, LocalTransactionProver, Prover, TransactionProverError}; @@ -40,7 +37,6 @@ use rstest::rstest; // HELPER FUNCTIONS // ================================================================================================ -#[cfg(test)] pub async fn prove_and_verify_transaction_deferred( executed_transaction: ExecutedTransaction, ) -> Result<(), TransactionVerifierError> { @@ -50,7 +46,6 @@ pub async fn prove_and_verify_transaction_deferred( } /// Proves `executed_transaction` locally, round-trips it and verifies it. -#[cfg(test)] pub async fn prove_and_verify_transaction_complete( executed_transaction: ExecutedTransaction, ) -> Result<(), TransactionVerifierError> { @@ -61,7 +56,6 @@ pub async fn prove_and_verify_transaction_complete( /// Proves `executed_transaction` locally, round-trips it and verifies it, returning the proven /// transaction together with its verification outcome. -#[cfg(test)] pub async fn prove_and_verify_transaction( executed_transaction: ExecutedTransaction, ) -> Result<(ProvenTransaction, VerificationOutcome), TransactionVerifierError> { @@ -216,7 +210,6 @@ async fn custom_execution_options_reach_the_vm() -> anyhow::Result<()> { Ok(()) } -#[cfg(test)] pub fn get_note_with_fungible_asset_and_script( fungible_asset: FungibleAsset, note_script: &str, @@ -235,7 +228,6 @@ pub fn get_note_with_fungible_asset_and_script( /// Consumes a single authenticated input note against `account_id` in its own transaction and /// commits the resulting block, so the note's effects are visible to subsequent transactions. -#[cfg(test)] pub async fn consume_note( mock_chain: &mut MockChain, account_id: AccountId, @@ -257,7 +249,6 @@ pub async fn consume_note( /// The typed note builders of the standard config notes fix the note type to /// [`NoteType::Public`], so this is how a sender would hand-craft a private note that is /// otherwise indistinguishable from a legitimate config note. -#[cfg(test)] pub fn into_private_note(note: Note) -> Note { let metadata = PartialNoteMetadata::new(note.metadata().sender(), NoteType::Private) .with_tag(note.metadata().tag());