diff --git a/crates/block-producer/src/errors.rs b/crates/block-producer/src/errors.rs index 3bc17f0c19..413ee7747c 100644 --- a/crates/block-producer/src/errors.rs +++ b/crates/block-producer/src/errors.rs @@ -11,6 +11,7 @@ use miden_node_store::{ use miden_protocol::Word; use miden_protocol::account::AccountId; use miden_protocol::asset::AssetId; +use miden_protocol::batch::BatchId; use miden_protocol::block::BlockNumber; use miden_protocol::crypto::utils::DeserializationError; use miden_protocol::errors::{ProposedBatchError, ProposedBlockError, ProvenBatchError}; @@ -96,6 +97,9 @@ pub enum MempoolSubmissionError { transaction_id: TransactionId, fee_asset_id: AssetId, }, + + #[error("user batch proof ID {proof_id} does not match transaction batch ID {batch_id}")] + BatchIdMismatch { proof_id: BatchId, batch_id: BatchId }, } // Mempool submission conflicts with current state diff --git a/crates/block-producer/src/mempool/graph/batch.rs b/crates/block-producer/src/mempool/graph/batch.rs index e7c5c7d1b1..0bcb4623e3 100644 --- a/crates/block-producer/src/mempool/graph/batch.rs +++ b/crates/block-producer/src/mempool/graph/batch.rs @@ -50,11 +50,12 @@ impl GraphNode for SelectedBatch { // BATCH GRAPH // ================================================================================================ -/// Tracks [`SelectedBatch`] instances that are pending proof generation. +/// Tracks [`SelectedBatch`] instances that are waiting for inclusion in a block. /// /// Batches form nodes in the underlying [`Graph`]. Edges between batches capture dependencies /// introduced by shared resources (nullifiers, notes, and account states). The graph remains a DAG /// by requiring that each batch builds on top of the state created by previously inserted batches. +/// Sequencer-built batches wait for a proof. User-proven batches include their proof when inserted. #[derive(Clone, Debug, PartialEq, Default)] pub struct BatchGraph { inner: Graph, diff --git a/crates/block-producer/src/mempool/graph/transaction.rs b/crates/block-producer/src/mempool/graph/transaction.rs index b82f20ed6a..58c0ace981 100644 --- a/crates/block-producer/src/mempool/graph/transaction.rs +++ b/crates/block-producer/src/mempool/graph/transaction.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use miden_protocol::Word; use miden_protocol::account::AccountId; -use miden_protocol::batch::BatchId; +use miden_protocol::batch::{BatchId, ProvenBatch}; use miden_protocol::block::BlockNumber; use miden_protocol::note::Nullifier; use miden_protocol::transaction::{OutputNote, TransactionId}; @@ -73,7 +73,7 @@ impl GraphNode for Arc { // TRANSACTION GRAPH // ================================================================================================ -/// Tracks all [`AuthenticatedTransaction`]s that are waiting to be included in a batch. +/// Tracks standalone transactions and transactions from user-proven batches. /// /// Each transaction is a node in the underlying [`Graph`]. A directed edge from transaction `P` /// to transaction `C` exists when `C` depends on state produced by `P` — for example, `C` @@ -147,13 +147,14 @@ impl TransactionGraph { Some((creator.id(), output_note)) } - /// Appends the transactions into the graph as an atomic unit. + /// Appends a user-proven batch to the graph as an atomic unit. /// /// These transactions can only be selected as a batch, and are reverted and pruned together. pub fn append_user_batch( &mut self, batch: &[Arc], parameters: BatchParameters, + proof: Arc, ) -> Result<(), StateConflict> { let batch_id = BatchId::from_transactions(batch.iter().map(|tx| tx.raw_proven_transaction())); @@ -173,29 +174,24 @@ impl TransactionGraph { } let txs = batch.iter().map(GraphNode::id).collect::>(); - self.user_batches.insert(batch_id, txs, parameters); + self.user_batches.insert(batch_id, txs, parameters, proof); Ok(()) } - pub fn select_any_batch( + pub fn select_any_internal_batch( &mut self, budget: BatchBudget, internal_parameters: BatchParameters, ) -> Option { - self.select_user_batch() - .or_else(|| self.select_internal_batch(budget, internal_parameters).into_batch()) + self.select_internal_batch(budget, internal_parameters).into_batch() } - pub fn select_full_batch( + pub fn select_full_internal_batch( &mut self, budget: BatchBudget, internal_parameters: BatchParameters, ) -> Option { - if let Some(user_batch) = self.select_user_batch() { - return Some(user_batch); - } - match self.select_internal_batch(budget, internal_parameters) { BatchSelection::Full(batch) => Some(batch), BatchSelection::Partial(batch) => { @@ -213,7 +209,7 @@ impl TransactionGraph { } } - fn select_user_batch(&mut self) -> Option { + pub fn select_user_batch(&mut self) -> Option<(SelectedBatch, Arc)> { let candidate_batches = self.user_batches.batches().copied().collect::>(); for candidate in candidate_batches { if let Some(batch) = self.try_select_user_batch_candidate(candidate) { @@ -232,8 +228,12 @@ impl TransactionGraph { /// /// Transactions can fail selection if they depend on any external transactions that have /// not yet been selected. - fn try_select_user_batch_candidate(&mut self, candidate: BatchId) -> Option { - let (txs, parameters) = self.user_batches.get(&candidate)?; + fn try_select_user_batch_candidate( + &mut self, + candidate: BatchId, + ) -> Option<(SelectedBatch, Arc)> { + let (txs, parameters, proof) = self.user_batches.get(&candidate)?; + let proof = Arc::clone(proof); let mut selected = SelectedBatch::builder(parameters); for tx in txs { @@ -252,7 +252,7 @@ impl TransactionGraph { } assert!(!selected.is_empty(), "User batch should not be empty"); - Some(selected.build()) + Some((selected.build(), proof)) } fn select_internal_batch( @@ -493,14 +493,21 @@ struct BatchTxMap { struct UserBatch { txs: Vec, parameters: BatchParameters, + proof: Arc, } impl BatchTxMap { - fn insert(&mut self, batch: BatchId, txs: Vec, parameters: BatchParameters) { + fn insert( + &mut self, + batch: BatchId, + txs: Vec, + parameters: BatchParameters, + proof: Arc, + ) { for tx in &txs { assert!(self.by_tx.insert(*tx, batch).is_none()); } - assert!(self.by_batch.insert(batch, UserBatch { txs, parameters }).is_none()); + assert!(self.by_batch.insert(batch, UserBatch { txs, parameters, proof }).is_none()); } fn remove(&mut self, batch: &BatchId) -> Vec { @@ -522,8 +529,13 @@ impl BatchTxMap { self.by_tx.get(tx) } - fn get(&self, batch: &BatchId) -> Option<(&[TransactionId], BatchParameters)> { - self.by_batch.get(batch).map(|batch| (batch.txs.as_slice(), batch.parameters)) + fn get( + &self, + batch: &BatchId, + ) -> Option<(&[TransactionId], BatchParameters, &Arc)> { + self.by_batch + .get(batch) + .map(|batch| (batch.txs.as_slice(), batch.parameters, &batch.proof)) } fn contains_tx(&self, tx: &TransactionId) -> bool { diff --git a/crates/block-producer/src/mempool/mod.rs b/crates/block-producer/src/mempool/mod.rs index 4148a357e9..e141d0dfb9 100644 --- a/crates/block-producer/src/mempool/mod.rs +++ b/crates/block-producer/src/mempool/mod.rs @@ -274,6 +274,10 @@ impl Mempool { Ok(self.committed_chain_tip) } + /// Adds a user-proven batch to the mempool. + /// + /// The batch becomes available for block selection when its transaction dependencies are + /// selected. #[miden_instrument( target = COMPONENT, name = "mempool.add_user_batch", @@ -282,6 +286,7 @@ impl Mempool { &mut self, txs: &[Arc], parameters: BatchParameters, + proof: Arc, ) -> Result { assert!(!txs.is_empty(), "Cannot have a batch with no transactions"); @@ -300,14 +305,20 @@ impl Mempool { } } + let batch_id = BatchId::from_transactions(txs.iter().map(|tx| tx.raw_proven_transaction())); + if proof.id() != batch_id { + return Err(MempoolSubmissionError::BatchIdMismatch { proof_id: proof.id(), batch_id }); + } + for tx in txs { self.authentication_staleness_check(tx.authentication_height())?; self.expiration_check(tx.expires_at())?; } self.transactions - .append_user_batch(txs, parameters) + .append_user_batch(txs, parameters, proof) .map_err(MempoolSubmissionError::StateConflict)?; + self.promote_user_batches(); let telemetry = self.telemetry(); miden_span_record!( @@ -326,7 +337,7 @@ impl Mempool { Ok(self.committed_chain_tip) } - /// Returns a set of transactions for the next batch. + /// Returns a set of standalone transactions for the next sequencer-built batch. /// /// Transactions are returned in a valid execution ordering. /// @@ -336,11 +347,15 @@ impl Mempool { name = "mempool.select_any_batch", )] pub fn select_any_batch(&mut self) -> Option { + self.promote_user_batches(); let parameters = BatchParameters { reference_block: self.committed_chain_tip, }; - let batch = self.transactions.select_any_batch(self.config.batch_budget, parameters)?; + let batch = self + .transactions + .select_any_internal_batch(self.config.batch_budget, parameters)?; let batch = self.append_selected_batch(batch); + self.promote_user_batches(); let telemetry = self.telemetry(); miden_span_record!( mempool.transactions.uncommitted = telemetry.uncommitted_transactions, @@ -354,21 +369,24 @@ impl Mempool { Some(batch) } - /// Returns a full set of transactions for the next batch. + /// Returns a full set of standalone transactions for the next sequencer-built batch. /// - /// User batches count as full because they are externally chosen atomic batches. - /// Non-user batches are only returned when the selected set saturates the batch budget or when + /// The transactions are only returned when the selected set saturates the batch budget or when /// another selectable transaction cannot fit into the remaining budget. #[miden_instrument( target = COMPONENT, name = "mempool.select_full_batch", )] pub fn select_full_batch(&mut self) -> Option { + self.promote_user_batches(); let parameters = BatchParameters { reference_block: self.committed_chain_tip, }; - let batch = self.transactions.select_full_batch(self.config.batch_budget, parameters)?; + let batch = self + .transactions + .select_full_internal_batch(self.config.batch_budget, parameters)?; let batch = self.append_selected_batch(batch); + self.promote_user_batches(); let telemetry = self.telemetry(); miden_span_record!( mempool.transactions.uncommitted = telemetry.uncommitted_transactions, @@ -389,6 +407,14 @@ impl Mempool { batch } + /// Moves selectable user-proven batches into the batch graph. + fn promote_user_batches(&mut self) { + while let Some((batch, proof)) = self.transactions.select_user_batch() { + self.append_selected_batch(batch); + self.batches.submit_proof(proof); + } + } + /// Drops the proposed batch and all of its descendants. /// /// The transactions are re-queued for inclusion in a batch. Additionally, the batch's diff --git a/crates/block-producer/src/mempool/tests/add_user_batch.rs b/crates/block-producer/src/mempool/tests/add_user_batch.rs index 3a1ed14aa8..78c5e8fcb2 100644 --- a/crates/block-producer/src/mempool/tests/add_user_batch.rs +++ b/crates/block-producer/src/mempool/tests/add_user_batch.rs @@ -2,7 +2,7 @@ use std::num::NonZeroUsize; use std::sync::Arc; use assert_matches::assert_matches; -use miden_protocol::batch::BatchId; +use miden_protocol::batch::{BatchId, ProvenBatch}; use miden_protocol::block::BlockNumber; use pretty_assertions::assert_eq; @@ -11,14 +11,10 @@ use crate::domain::transaction::AuthenticatedTransaction; use crate::errors::{MempoolSubmissionError, StateConflict}; use crate::mempool::Mempool; use crate::test_utils::MockProvenTxBuilder; +use crate::test_utils::batch::TransactionBatchConstructor; -/// This checks that transactions from a user batch remain as the same batch upon selection. -/// -/// Since the selection process is random, its difficult to test this directly, but this at -/// least acts as a smoke test. We select two batches and check that one of them is the user -/// batch. #[test] -fn user_batch_is_isolated_from_other_transactions() { +fn user_batch_bypasses_batch_proving() { let (mut uut, _) = Mempool::for_tests(); let conventional_a = build_tx(MockProvenTxBuilder::with_account_index(200)); @@ -30,22 +26,14 @@ fn user_batch_is_isolated_from_other_transactions() { let user_batch_txs = MockProvenTxBuilder::sequential(); let user_batch_id = BatchId::from_transactions(user_batch_txs.iter().map(|tx| tx.raw_proven_transaction())); + let user_proof = Arc::new(ProvenBatch::mocked_from_transactions( + user_batch_txs.iter().map(|tx| tx.raw_proven_transaction()), + )); let user_parameters = BatchParameters { reference_block: 42.into() }; - uut.add_user_batch(&user_batch_txs, user_parameters).unwrap(); - - let batch_a = uut.select_any_batch().unwrap(); - let batch_b = uut.select_any_batch().unwrap(); - - let (user, conventional) = if batch_a.id() == user_batch_id { - (batch_a, batch_b) - } else { - (batch_b, batch_a) - }; - - assert_eq!(user.id(), user_batch_id); - assert_eq!(user.transactions(), user_batch_txs.as_slice()); - assert_eq!(user.parameters(), user_parameters); + uut.add_user_batch(&user_batch_txs, user_parameters, Arc::clone(&user_proof)) + .unwrap(); + let conventional = uut.select_any_batch().unwrap(); assert_eq!(conventional.transactions().len(), 2); assert!(conventional.transactions().contains(&conventional_a)); assert!(conventional.transactions().contains(&conventional_b)); @@ -53,6 +41,10 @@ fn user_batch_is_isolated_from_other_transactions() { conventional.parameters(), BatchParameters { reference_block: BlockNumber::GENESIS } ); + + assert_eq!(user_proof.id(), user_batch_id); + let block = uut.select_block(); + assert_eq!(block.batches, vec![user_proof]); } #[test] @@ -61,11 +53,26 @@ fn user_batch_respects_batch_budget() { uut.config.batch_budget.transactions = 1; let user_batch_txs = MockProvenTxBuilder::sequential(); - let result = uut.add_user_batch(&user_batch_txs[..2], BatchParameters::for_tests()); + let result = add_user_batch(&mut uut, &user_batch_txs[..2], BatchParameters::for_tests()); assert_matches!(result, Err(MempoolSubmissionError::CapacityExceeded)); } +#[test] +fn user_batch_rejects_a_mismatched_proof() { + let (mut uut, reference) = Mempool::for_tests(); + let [batch_tx, proof_tx] = [ + build_tx(MockProvenTxBuilder::with_account_index(40)), + build_tx(MockProvenTxBuilder::with_account_index(41)), + ]; + let proof = ProvenBatch::mocked_from_transactions([proof_tx.raw_proven_transaction()]); + + let result = uut.add_user_batch(&[batch_tx], BatchParameters::for_tests(), Arc::new(proof)); + + assert_matches!(result, Err(MempoolSubmissionError::BatchIdMismatch { .. })); + assert_eq!(uut, reference); +} + #[test] fn user_batch_capacity_counts_batched_uncommitted_transactions() { let (mut uut, _) = Mempool::for_tests(); @@ -77,21 +84,22 @@ fn user_batch_capacity_counts_batched_uncommitted_transactions() { uut.select_any_batch().unwrap(); assert_matches!( - uut.add_user_batch(&user_batch, BatchParameters::for_tests()), + add_user_batch(&mut uut, &user_batch, BatchParameters::for_tests()), Err(MempoolSubmissionError::CapacityExceeded) ); } #[test] -fn user_batch_counts_as_full_batch() { +fn user_batch_is_not_selected_for_proving() { let (mut uut, _) = Mempool::for_tests(); uut.config.batch_budget.transactions = 3; let user_batch_txs = MockProvenTxBuilder::sequential(); - uut.add_user_batch(&user_batch_txs[..1], BatchParameters::for_tests()).unwrap(); + add_user_batch(&mut uut, &user_batch_txs[..1], BatchParameters::for_tests()).unwrap(); - let batch = uut.select_full_batch().unwrap(); - assert_eq!(batch.transactions(), &user_batch_txs[..1]); + assert!(uut.select_any_batch().is_none()); + assert!(uut.select_full_batch().is_none()); + assert_eq!(uut.select_block().batches.len(), 1); } #[test] @@ -101,7 +109,8 @@ fn user_batch_with_internal_state_conflicts_are_rejected() { let conflicting_a = tx_with_nullifiers(10, 0..1); let conflicting_b = tx_with_nullifiers(11, 0..1); - let result = uut.add_user_batch( + let result = add_user_batch( + &mut uut, &[conflicting_a.clone(), conflicting_b.clone()], BatchParameters::for_tests(), ); @@ -125,8 +134,11 @@ fn user_batch_conflicts_with_existing_state_are_rejected() { let conflicting = tx_with_nullifiers(21, 5..6); let companion = tx_with_nullifiers(22, 6..7); - let result = - uut.add_user_batch(&[conflicting.clone(), companion.clone()], BatchParameters::for_tests()); + let result = add_user_batch( + &mut uut, + &[conflicting.clone(), companion.clone()], + BatchParameters::for_tests(), + ); assert_matches!( result, @@ -140,6 +152,16 @@ fn build_tx(builder: MockProvenTxBuilder) -> Arc { Arc::new(AuthenticatedTransaction::from_inner(builder.build())) } +fn add_user_batch( + mempool: &mut Mempool, + txs: &[Arc], + parameters: BatchParameters, +) -> Result { + let proof = + ProvenBatch::mocked_from_transactions(txs.iter().map(|tx| tx.raw_proven_transaction())); + mempool.add_user_batch(txs, parameters, Arc::new(proof)) +} + fn tx_with_nullifiers( account_index: u32, range: std::ops::Range, diff --git a/crates/block-producer/src/server/mod.rs b/crates/block-producer/src/server/mod.rs index ef918452f7..1aa35620b6 100644 --- a/crates/block-producer/src/server/mod.rs +++ b/crates/block-producer/src/server/mod.rs @@ -8,7 +8,7 @@ use miden_node_tracing::{debug, error, info, miden_instrument}; use miden_node_utils::formatting::{format_input_notes, format_output_notes}; use miden_node_utils::shutdown::CancellationToken; use miden_node_utils::tasks::Tasks; -use miden_protocol::batch::ProposedBatch; +use miden_protocol::batch::{ProposedBatch, ProvenBatch}; use miden_protocol::block::BlockNumber; use miden_protocol::transaction::ProvenTransaction; use tokio::sync::{Mutex, RwLock}; @@ -376,6 +376,7 @@ impl BlockProducerApi { )] pub async fn submit_proven_tx_batch( &self, + proof: ProvenBatch, batch: ProposedBatch, ) -> Result { // We assume that the rpc component has verified everything, including the transaction @@ -391,7 +392,7 @@ impl BlockProducerApi { ); } - self.submit_authenticated_tx_batch(batch, inputs).await + self.submit_authenticated_tx_batch(proof, batch, inputs).await } /// Adds a batch whose transactions have already been authenticated against the store to the @@ -409,6 +410,7 @@ impl BlockProducerApi { #[expect(clippy::let_and_return)] pub async fn submit_authenticated_tx_batch( &self, + proof: ProvenBatch, batch: ProposedBatch, inputs: Vec, ) -> Result { @@ -436,7 +438,7 @@ impl BlockProducerApi { let result = shared_mempool .lock() .map_err(MempoolSubmissionError::MempoolPoisoned)? - .add_user_batch(&txs, parameters); + .add_user_batch(&txs, parameters, Arc::new(proof)); result } diff --git a/crates/rpc/src/server/api/submit_auth_tx_batch.rs b/crates/rpc/src/server/api/submit_auth_tx_batch.rs index a3ab5184e3..f3d05e42a9 100644 --- a/crates/rpc/src/server/api/submit_auth_tx_batch.rs +++ b/crates/rpc/src/server/api/submit_auth_tx_batch.rs @@ -3,7 +3,7 @@ use miden_node_proto::generated::server::sequencer_api; use miden_node_proto::{DecodeMessage, VerifyWith, generated as proto}; use miden_node_tracing::ErrorReport; use miden_node_tracing::spawn::spawn_blocking_in_current_span; -use miden_protocol::batch::ProposedBatch; +use miden_protocol::batch::{ProposedBatch, ProvenBatch}; use tonic::Status; use super::SequencerInternalService; @@ -29,7 +29,7 @@ impl sequencer_api::SubmitAuthenticatedTxBatch for SequencerInternalService { _metadata: &tonic::metadata::MetadataMap, _extensions: &tonic::codegen::http::Extensions, ) -> tonic::Result { - let (batch, inputs) = + let (proof, batch, inputs) = spawn_blocking_in_current_span(move || decode_authenticated_transaction_batch(request)) .await .map_err(|err| { @@ -41,7 +41,7 @@ impl sequencer_api::SubmitAuthenticatedTxBatch for SequencerInternalService { } self.block_producer - .submit_authenticated_tx_batch(batch, inputs) + .submit_authenticated_tx_batch(proof, batch, inputs) .await .map(Into::into) .map_err(Into::into) @@ -50,7 +50,7 @@ impl sequencer_api::SubmitAuthenticatedTxBatch for SequencerInternalService { fn decode_authenticated_transaction_batch( request: proto::sequencer::AuthenticatedTransactionBatch, -) -> tonic::Result<(ProposedBatch, Vec)> { +) -> tonic::Result<(ProvenBatch, ProposedBatch, Vec)> { let proposed_batch = request .proposed_batch .ok_or_else(|| Status::invalid_argument("missing `proposed_batch` field"))?; @@ -60,6 +60,14 @@ fn decode_authenticated_transaction_batch( .verify_with(miden_protocol::MIN_PROOF_SECURITY_LEVEL) .map_err(|err| Status::invalid_argument(format!("invalid proposed_batch: {err}")))?; + let proof = request + .batch_proof + .ok_or_else(|| Status::invalid_argument("missing `batch_proof` field"))? + .decode_fields() + .map_err(|err| Status::invalid_argument(format!("invalid batch_proof: {err}")))? + .verify_with(&batch) + .map_err(|err| Status::invalid_argument(format!("invalid batch_proof: {err}")))?; + if batch.transactions().len() != request.auth_inputs.len() { return Err(Status::invalid_argument(format!( "Number of inputs {} does not match number of transactions {} in batch", @@ -75,5 +83,5 @@ fn decode_authenticated_transaction_batch( .collect::, _>>() .map_err(|err| Status::invalid_argument(err.as_report_context("invalid auth_inputs")))?; - Ok((batch, inputs)) + Ok((proof, batch, inputs)) } diff --git a/crates/rpc/src/server/api/submit_proven_tx_batch.rs b/crates/rpc/src/server/api/submit_proven_tx_batch.rs index 56514ebd67..457d0719e5 100644 --- a/crates/rpc/src/server/api/submit_proven_tx_batch.rs +++ b/crates/rpc/src/server/api/submit_proven_tx_batch.rs @@ -127,7 +127,7 @@ impl proto::server::rpc_api::SubmitProvenTxBatch for RpcService { } // Verify batch transaction proofs. - verify_batch_proof(proven_batch, &proposed_batch).await?; + verify_batch_proof(&proven_batch, &proposed_batch).await?; match &self.backend { RpcBackend::Sequencer { block_producer, validators, .. } => { @@ -138,7 +138,7 @@ impl proto::server::rpc_api::SubmitProvenTxBatch for RpcService { ) .await?; block_producer - .submit_proven_tx_batch(proposed_batch) + .submit_proven_tx_batch(proven_batch, proposed_batch) .await .map(Into::into) .map_err(Into::into) @@ -149,6 +149,7 @@ impl proto::server::rpc_api::SubmitProvenTxBatch for RpcService { self.submit_authenticated_batch_to_sequencer( pre_auth.validators().as_slice(), pre_auth.sequencer().clone(), + proven_batch, proposed_batch, &request.sealed_transaction_inputs, ) @@ -181,6 +182,7 @@ impl RpcService { &self, validators: &[ValidatorClient], mut sequencer: SequencerClient, + proven_batch: ProvenBatch, proposed_batch: ProposedBatch, sealed_transaction_inputs: &[proto::submission::SealedTransactionInputs], ) -> tonic::Result { @@ -197,6 +199,7 @@ impl RpcService { let authenticated_batch = proto::sequencer::AuthenticatedTransactionBatch { proposed_batch: Some((&proposed_batch).into()), auth_inputs, + batch_proof: Some((&proven_batch).into()), }; sequencer .submit_authenticated_tx_batch(authenticated_batch) @@ -209,7 +212,7 @@ impl RpcService { /// /// Errors on id mismatch, or the proof cannot be verified [`MIN_PROOF_SECURITY_LEVEL`] async fn verify_batch_proof( - proven_batch: ProvenBatch, + proven_batch: &ProvenBatch, proposed_batch: &ProposedBatch, ) -> tonic::Result<()> { if proven_batch.id() != proposed_batch.id() { diff --git a/crates/rpc/src/tests.rs b/crates/rpc/src/tests.rs index f91f981407..c3e31ce5f0 100644 --- a/crates/rpc/src/tests.rs +++ b/crates/rpc/src/tests.rs @@ -1520,6 +1520,7 @@ async fn sequencer_authenticated_rpc_accepts_user_batch_without_fee_notes() { } let request = proto::sequencer::AuthenticatedTransactionBatch { proposed_batch: fixture.request.proposed_batch, + batch_proof: fixture.request.batch, auth_inputs, }; @@ -1537,6 +1538,7 @@ async fn authenticated_batch_defers_validation_to_async_handler() { let request = proto::sequencer::AuthenticatedTransactionBatch { proposed_batch: Some(proto::transaction::ProposedBatch::default()), auth_inputs: Vec::new(), + batch_proof: None, }; let input = ::decode(request) diff --git a/crates/rpc/src/tests/allowlist.rs b/crates/rpc/src/tests/allowlist.rs index 7382e6c7cc..db891ad848 100644 --- a/crates/rpc/src/tests/allowlist.rs +++ b/crates/rpc/src/tests/allowlist.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use miden_node_proto::generated::submission::SealedTransactionInputs; -use miden_protocol::batch::{ProposedBatch, ProvenBatch}; +use miden_protocol::batch::ProposedBatch; use miden_standards::account::auth::NetworkAccount; use miden_standards::account::fees::{BasicConstantFeePolicy, FeePolicyManager}; @@ -180,18 +180,15 @@ async fn submission_endpoints_reject_unregistered_creation_without_partial_batch allowlist.add_account(transactions[0].account_id()).await.unwrap(); - let header = batch.reference_block_header(); - let proven_batch = ProvenBatch::new_unchecked( - batch.id(), - header.commitment(), - header.block_num(), - batch.account_updates().clone(), - batch.input_notes().clone(), - batch.output_notes().to_vec(), - batch.batch_expiration_block_num(), - batch.transaction_headers(), - miden_protocol::testing::dummy_execution_proof(), - ) + let proven_batch = spawn_blocking_in_current_span({ + let batch = batch.clone(); + move || { + let executed = BatchExecutor::new().execute(batch)?; + LocalBatchProver::default().prove(executed) + } + }) + .await + .unwrap() .unwrap(); let tx = proto::sequencer::AuthenticatedTransaction { transaction: Some(transactions[1].as_ref().into()), @@ -199,6 +196,7 @@ async fn submission_endpoints_reject_unregistered_creation_without_partial_batch }; let authenticated_batch = proto::sequencer::AuthenticatedTransactionBatch { proposed_batch: Some((&batch).into()), + batch_proof: Some((&proven_batch).into()), auth_inputs: transactions .iter() .map(|tx| proto::sequencer::AuthInputs { diff --git a/proto/proto/internal/sequencer.proto b/proto/proto/internal/sequencer.proto index a00cb8e327..a05fc6e796 100644 --- a/proto/proto/internal/sequencer.proto +++ b/proto/proto/internal/sequencer.proto @@ -42,7 +42,7 @@ message AuthenticatedTransaction { fixed32 authentication_height = 4; } -// A proposed batch together with the inputs each of its transactions was authenticated against. +// A proven batch together with its proposed contents and authenticated transaction inputs. message AuthenticatedTransactionBatch { // The proposed batch. // @@ -52,6 +52,9 @@ message AuthenticatedTransactionBatch { // // Must match the transaction ordering in the batch. repeated AuthInputs auth_inputs = 2; + + // The proven batch. + transaction.ProvenBatch batch_proof = 3; } // The store-derived inputs a transaction was authenticated against.