Skip to content

Commit

Permalink
Migrate batch kernel parts from miden-node (#1112)
Browse files Browse the repository at this point in the history
* feat: Add miden-batch-prover crate

* feat: Add `BatchId`

* Introduce `AccountUpdateError`

* feat: Add `ProposedBatch`

* feat: Add `LocalBatchProver`

* feat: Add `ProvenBatch`

* feat: Migrate `LocalBatchProver` from node

* chore: Rename `NoteAuthenticationInfo`

* feat:Add batch expiration block num

* chore: Use core instead of std for `Display`

* feat: Migrate `MockProvenTxBuilder`

* feat: Test tx ordering in batches

* feat: Add `BatchAccountUpdate`

* chore: Extend test assertions

* feat: Refactor and document batch output note tracker

* feat: Add input/output notes commitment test

* feat: Remove `BlockNumber::from_usize`

* feat: Check for duplicate input notes

* feat: Add unauthenticated/authenticated scenario tests

* chore: Misc doc fixes

* feat: Move `ProposedBatch` and `ProvenBatch` to objects

* chore: Add changelog entry

* feat: Use MockChain for batch tests (partially)

* chore: Move most validation to `ProposedBatch`

* feat: Use MockChain for all tests

* feat: Implement note authentication and tests for it

* feat: Verify transactions in batch

* chore: Document error conditions on proposed batch

* feat: Check max input/output notes and account updates in batch

* feat: Check for duplicate transactions in batch

* feat: Add tests with a circular note dependency

* feat: Rename batch prover crate

* chore: Address review comments

* chore: Unbox the `BlockHeader`

* chore: Remove unused dependencies

* feat: Use `InputNotes` rather than `Vec`

* feat: Compute batch ID as sequential hash over tx ID and account ID

* chore: Remove `BlockInclusionProof` and `NoteInclusionProofs`

* chore: Address more review comments

* chore: Move tests into dedicated file

* feat: Add `block_num` field to `ProvenTransaction`

* feat: Add `ChainMmr::from_mmr`

* feat: Add `BatchAccountUpdate::into_parts`

* feat: Add `ProvenBatch::produced_nullifiers`

* feat: Add `BatchId::compute_from_ids`

* feat: Expose `BatchProveError` and rename `BatchProposeError`

* feat: Remove `compute` prefix from batch id constructors

* chore: Move `ChainMmr::from_mmr` behind test flag

* chore: Address review comments

* feat: Disallow empty transaction batches

* chore: Address review comments
  • Loading branch information
PhilippGackstatter authored Feb 4, 2025
1 parent 723e47c commit e82dee0
Show file tree
Hide file tree
Showing 32 changed files with 1,939 additions and 40 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Added an endpoint to the `miden-proving-service` to update the workers (#1107).
- Renamed the protobuf file of the transaction prover to `tx_prover.proto` (#1110).
- [BREAKING] Renamed `AccountData` to `AccountFile` (#1116).
- Implement transaction batch prover in Rust (#1112).

## 0.7.2 (2025-01-28) - `miden-objects` crate only

Expand Down
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolver = "2"
members = [
"bin/bench-tx",
"bin/proving-service",
"crates/miden-tx-batch-prover",
"crates/miden-lib",
"crates/miden-objects",
"crates/miden-proving-service-client",
Expand Down
161 changes: 161 additions & 0 deletions crates/miden-objects/src/batch/account_update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
use alloc::vec::Vec;

use vm_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
use vm_processor::{DeserializationError, Digest};

use crate::{
account::{delta::AccountUpdateDetails, AccountId},
errors::BatchAccountUpdateError,
transaction::{ProvenTransaction, TransactionId},
};

// BATCH ACCOUNT UPDATE
// ================================================================================================

/// Represents the changes made to an account resulting from executing a batch of transactions.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BatchAccountUpdate {
/// ID of the updated account.
account_id: AccountId,

/// Commitment to the state of the account before this update is applied.
///
/// Equal to `Digest::default()` for new accounts.
initial_state_commitment: Digest,

/// Commitment to the state of the account after this update is applied.
final_state_commitment: Digest,

/// IDs of all transactions that updated the account.
transactions: Vec<TransactionId>,

/// A set of changes which can be applied to the previous account state (i.e. `initial_state`)
/// to get the new account state. For private accounts, this is set to
/// [`AccountUpdateDetails::Private`].
details: AccountUpdateDetails,
}

impl BatchAccountUpdate {
// CONSTRUCTORS
// --------------------------------------------------------------------------------------------

/// Creates a [`BatchAccountUpdate`] by cloning the update and other details from the provided
/// [`ProvenTransaction`].
pub fn from_transaction(transaction: &ProvenTransaction) -> Self {
Self {
account_id: transaction.account_id(),
initial_state_commitment: transaction.account_update().init_state_hash(),
final_state_commitment: transaction.account_update().final_state_hash(),
transactions: vec![transaction.id()],
details: transaction.account_update().details().clone(),
}
}

// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------

/// Returns the ID of the updated account.
pub fn account_id(&self) -> AccountId {
self.account_id
}

/// Returns a commitment to the state of the account before this update is applied.
///
/// This is equal to [`Digest::default()`] for new accounts.
pub fn initial_state_commitment(&self) -> Digest {
self.initial_state_commitment
}

/// Returns a commitment to the state of the account after this update is applied.
pub fn final_state_commitment(&self) -> Digest {
self.final_state_commitment
}

/// Returns a slice of [`TransactionId`]s that updated this account's state.
pub fn transactions(&self) -> &[TransactionId] {
&self.transactions
}

/// Returns the contained [`AccountUpdateDetails`].
///
/// This update can be used to build the new account state from the previous account state.
pub fn details(&self) -> &AccountUpdateDetails {
&self.details
}

/// Returns `true` if the account update details are for a private account.
pub fn is_private(&self) -> bool {
self.details.is_private()
}

// MUTATORS
// --------------------------------------------------------------------------------------------

/// Merges the transaction's update into this account update.
///
/// # Errors
///
/// Returns an error if:
/// - The account ID of the merging transaction does not match the account ID of the existing
/// update.
/// - The merging transaction's initial state commitment does not match the final state
/// commitment of the current update.
/// - If the underlying [`AccountUpdateDetails::merge`] fails.
pub fn merge_proven_tx(
&mut self,
tx: &ProvenTransaction,
) -> Result<(), BatchAccountUpdateError> {
if self.account_id != tx.account_id() {
return Err(BatchAccountUpdateError::AccountUpdateIdMismatch {
transaction: tx.id(),
expected_account_id: self.account_id,
actual_account_id: tx.account_id(),
});
}

if self.final_state_commitment != tx.account_update().init_state_hash() {
return Err(BatchAccountUpdateError::AccountUpdateInitialStateMismatch(tx.id()));
}

self.details = self.details.clone().merge(tx.account_update().details().clone()).map_err(
|source_err| BatchAccountUpdateError::TransactionUpdateMergeError(tx.id(), source_err),
)?;
self.final_state_commitment = tx.account_update().final_state_hash();
self.transactions.push(tx.id());

Ok(())
}

// CONVERSIONS
// --------------------------------------------------------------------------------------------

/// Consumes the update and returns the non-[`Copy`] parts.
pub fn into_parts(self) -> (Vec<TransactionId>, AccountUpdateDetails) {
(self.transactions, self.details)
}
}

// SERIALIZATION
// ================================================================================================

impl Serializable for BatchAccountUpdate {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.account_id.write_into(target);
self.initial_state_commitment.write_into(target);
self.final_state_commitment.write_into(target);
self.transactions.write_into(target);
self.details.write_into(target);
}
}

impl Deserializable for BatchAccountUpdate {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
Ok(Self {
account_id: AccountId::read_from(source)?,
initial_state_commitment: Digest::read_from(source)?,
final_state_commitment: Digest::read_from(source)?,
transactions: <Vec<TransactionId>>::read_from(source)?,
details: AccountUpdateDetails::read_from(source)?,
})
}
}
65 changes: 65 additions & 0 deletions crates/miden-objects/src/batch/batch_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use alloc::{string::String, vec::Vec};

use vm_core::{Felt, ZERO};
use vm_processor::Digest;

use crate::{
account::AccountId,
transaction::{ProvenTransaction, TransactionId},
Hasher,
};

// BATCH ID
// ================================================================================================

/// Uniquely identifies a batch of transactions, i.e. both
/// [`ProposedBatch`](crate::batch::ProposedBatch) and [`ProvenBatch`](crate::batch::ProvenBatch).
///
/// This is a sequential hash of the tuple `(TRANSACTION_ID || [account_id_prefix,
/// account_id_suffix, 0, 0])` of all transactions and the accounts their executed against in the
/// batch.
#[derive(Debug, Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub struct BatchId(Digest);

impl BatchId {
/// Calculates a batch ID from the given set of transactions.
pub fn from_transactions<'tx, T>(txs: T) -> Self
where
T: Iterator<Item = &'tx ProvenTransaction>,
{
Self::from_ids(txs.map(|tx| (tx.id(), tx.account_id())))
}

/// Calculates a batch ID from the given transaction ID and account ID tuple.
pub fn from_ids(iter: impl Iterator<Item = (TransactionId, AccountId)>) -> Self {
let mut elements: Vec<Felt> = Vec::new();
for (tx_id, account_id) in iter {
elements.extend_from_slice(tx_id.as_elements());
let [account_id_prefix, account_id_suffix] = <[Felt; 2]>::from(account_id);
elements.extend_from_slice(&[account_id_prefix, account_id_suffix, ZERO, ZERO]);
}

Self(Hasher::hash_elements(&elements))
}

/// Returns the elements representation of this batch ID.
pub fn as_elements(&self) -> &[Felt] {
self.0.as_elements()
}

/// Returns the byte representation of this batch ID.
pub fn as_bytes(&self) -> [u8; 32] {
self.0.as_bytes()
}

/// Returns a big-endian, hex-encoded string.
pub fn to_hex(&self) -> String {
self.0.to_hex()
}
}

impl core::fmt::Display for BatchId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.to_hex())
}
}
12 changes: 12 additions & 0 deletions crates/miden-objects/src/batch/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
mod note_tree;
pub use note_tree::BatchNoteTree;

mod batch_id;
pub use batch_id::BatchId;

mod account_update;
pub use account_update::BatchAccountUpdate;

mod proven_batch;
pub use proven_batch::ProvenBatch;

mod proposed_batch;
pub use proposed_batch::ProposedBatch;
5 changes: 5 additions & 0 deletions crates/miden-objects/src/batch/note_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,9 @@ impl BatchNoteTree {
pub fn root(&self) -> RpoDigest {
self.0.root()
}

/// Returns the number of non-empty leaves in this tree.
pub fn num_leaves(&self) -> usize {
self.0.num_leaves()
}
}
Loading

0 comments on commit e82dee0

Please sign in to comment.