Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9f9619d
An outline
Mirko-von-Leipzig Mar 16, 2026
c1e8295
Impl graph skeleton
Mirko-von-Leipzig Mar 16, 2026
bfd590c
Impl batch skeletons
Mirko-von-Leipzig Mar 16, 2026
7ce6f61
Delete vestigial
Mirko-von-Leipzig Mar 16, 2026
ed8b0b5
Mempool::add_transaction
Mirko-von-Leipzig Mar 17, 2026
62f2637
Mempool::PartialEq
Mirko-von-Leipzig Mar 17, 2026
8ac5241
Mempool::select_batch
Mirko-von-Leipzig Mar 17, 2026
41dc2f2
Mempool::rollback_batch
Mirko-von-Leipzig Mar 17, 2026
a6f6c6b
Mempool::commit_batch
Mirko-von-Leipzig Mar 17, 2026
4f82e3a
Mempool::select_block
Mirko-von-Leipzig Mar 17, 2026
b0f568d
Mempool::pending_block
Mirko-von-Leipzig Mar 17, 2026
9c1cb0e
Mempool::prune
Mirko-von-Leipzig Mar 17, 2026
b838067
Mempool::revert_expired
Mirko-von-Leipzig Mar 17, 2026
be61536
Mempool::rollback_block
Mirko-von-Leipzig Mar 17, 2026
28e8890
Mempool::authentication_staleness_check
Mirko-von-Leipzig Mar 18, 2026
6fcfb8d
Drop old graph impl
Mirko-von-Leipzig Mar 18, 2026
cdcae37
Track selected internally in graph
Mirko-von-Leipzig Mar 18, 2026
51f9e99
Move append checks into graph
Mirko-von-Leipzig Mar 18, 2026
ffdaea7
Implement pruning
Mirko-von-Leipzig Mar 18, 2026
cda6296
Implement descendents
Mirko-von-Leipzig Mar 18, 2026
0dbcd5f
Don't pop roots
Mirko-von-Leipzig Mar 18, 2026
e049301
Consider selected for expiration reversion
Mirko-von-Leipzig Mar 18, 2026
94aa884
Requeue transactions
Mirko-von-Leipzig Mar 18, 2026
682c8af
Some telemetry
Mirko-von-Leipzig Mar 18, 2026
a71d097
Fix account state tracking
Mirko-von-Leipzig Mar 19, 2026
a197fe6
Fix reversion
Mirko-von-Leipzig Mar 19, 2026
8a1b7fb
Lints
Mirko-von-Leipzig Mar 19, 2026
14fee6c
Move account states into separate file
Mirko-von-Leipzig Mar 19, 2026
c3fa580
Submodules
Mirko-von-Leipzig Mar 19, 2026
c96e116
Fix reverts
Mirko-von-Leipzig Mar 19, 2026
48103a4
flatten modules
Mirko-von-Leipzig Mar 19, 2026
73d3c00
Make selection candidates explicit
Mirko-von-Leipzig Mar 19, 2026
7c5ed5d
Fix batch word commitment
Mirko-von-Leipzig Mar 19, 2026
424a5bc
AI suggestions
Mirko-von-Leipzig Mar 20, 2026
93dd09d
Re-enable lints
Mirko-von-Leipzig Mar 20, 2026
a2097dd
Update errors
Mirko-von-Leipzig Mar 23, 2026
348a528
Fix bugs found by tests
Mirko-von-Leipzig Mar 23, 2026
0334290
Re-add telemetry
Mirko-von-Leipzig Mar 23, 2026
8bba7a8
AI tests
Mirko-von-Leipzig Mar 23, 2026
fa897ae
Address Serge comments
Mirko-von-Leipzig Mar 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 25 additions & 13 deletions crates/block-producer/src/domain/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::sync::Arc;
use miden_protocol::Word;
use miden_protocol::account::AccountId;
use miden_protocol::batch::BatchId;
use miden_protocol::transaction::TransactionId;
use miden_protocol::block::BlockNumber;

use crate::domain::transaction::AuthenticatedTransaction;

Expand All @@ -22,7 +22,7 @@ use crate::domain::transaction::AuthenticatedTransaction;
pub(crate) struct SelectedBatch {
txs: Vec<Arc<AuthenticatedTransaction>>,
id: BatchId,
account_updates: HashMap<AccountId, (Word, Word)>,
account_updates: HashMap<AccountId, (Word, Word, Option<Word>)>,
}

impl SelectedBatch {
Expand All @@ -43,20 +43,33 @@ impl SelectedBatch {
}

/// The aggregated list of account transitions this batch causes given as tuples of `(AccountId,
/// initial commitment, final commitment)`.
/// initial commitment, final commitment, Option<store commitment>)`.
///
/// Note that the updates are aggregated, i.e. only a single update per account is possible, and
/// transaction updates to an account of `a -> b -> c` will result in a single `a -> c`.
pub(crate) fn account_updates(&self) -> impl Iterator<Item = (AccountId, Word, Word)> {
self.account_updates.iter().map(|(account, (from, to))| (*account, *from, *to))
pub(crate) fn account_updates(
&self,
) -> impl Iterator<Item = (AccountId, Word, Word, Option<Word>)> {
self.account_updates
.iter()
.map(|(account, (from, to, store))| (*account, *from, *to, *store))
}

pub(crate) fn expires_at(&self) -> BlockNumber {
self.txs
.iter()
.map(|tx| tx.expires_at().as_u32())
.min()
.unwrap_or(u32::MAX)
.into()
}
}

/// A builder to construct a [`SelectedBatch`].
#[derive(Clone, Default)]
pub(crate) struct SelectedBatchBuilder {
pub(crate) txs: Vec<Arc<AuthenticatedTransaction>>,
pub(crate) account_updates: HashMap<AccountId, (Word, Word)>,
pub(crate) account_updates: HashMap<AccountId, (Word, Word, Option<Word>)>,
}

impl SelectedBatchBuilder {
Expand All @@ -71,7 +84,7 @@ impl SelectedBatchBuilder {
let update = tx.account_update();
self.account_updates
.entry(update.account_id())
.and_modify(|(_, to)| {
.and_modify(|(_from, to, _store)| {
assert!(
to == &update.initial_state_commitment(),
"Cannot select transaction {} as its initial commitment {} for account {} does \
Expand All @@ -84,16 +97,15 @@ not match the current commitment {}",

*to = update.final_state_commitment();
})
.or_insert((update.initial_state_commitment(), update.final_state_commitment()));
.or_insert((
update.initial_state_commitment(),
update.final_state_commitment(),
tx.store_account_state(),
));

self.txs.push(tx);
}

/// Returns `true` if the batch contains the given transaction already.
pub(crate) fn contains(&self, target: &TransactionId) -> bool {
self.txs.iter().any(|tx| &tx.id() == target)
}

/// Returns `true` if it contains no transactions.
pub(crate) fn is_empty(&self) -> bool {
self.txs.is_empty()
Expand Down
6 changes: 3 additions & 3 deletions crates/block-producer/src/domain/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use miden_protocol::block::BlockNumber;
use miden_protocol::note::{NoteHeader, Nullifier};
use miden_protocol::transaction::{OutputNote, ProvenTransaction, TransactionId, TxAccountUpdate};

use crate::errors::VerifyTxError;
use crate::errors::StateConflict;
use crate::store::TransactionInputs;

/// A transaction who's proof has been verified, and which has been authenticated against the store.
Expand Down Expand Up @@ -48,13 +48,13 @@ impl AuthenticatedTransaction {
pub fn new_unchecked(
tx: ProvenTransaction,
inputs: TransactionInputs,
) -> Result<AuthenticatedTransaction, VerifyTxError> {
) -> Result<AuthenticatedTransaction, StateConflict> {
let nullifiers_already_spent = tx
.nullifiers()
.filter(|nullifier| inputs.nullifiers.get(nullifier).copied().flatten().is_some())
.collect::<Vec<_>>();
if !nullifiers_already_spent.is_empty() {
return Err(VerifyTxError::InputNotesAlreadyConsumed(nullifiers_already_spent));
return Err(StateConflict::NullifiersAlreadyExist(nullifiers_already_spent));
}

Ok(AuthenticatedTransaction {
Expand Down
111 changes: 22 additions & 89 deletions crates/block-producer/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,77 +34,14 @@ pub enum BlockProducerError {
},
}

// Transaction verification errors
// =================================================================================================

#[derive(Debug, Error)]
pub enum VerifyTxError {
/// Another transaction already consumed the notes with given nullifiers
#[error(
"input notes with given nullifiers were already consumed by another transaction: {0:?}"
)]
InputNotesAlreadyConsumed(Vec<Nullifier>),

/// Unauthenticated transaction notes were not found in the store or in outputs of in-flight
/// transactions
#[error(
"unauthenticated transaction note commitments were not found in the store or in outputs of in-flight transactions: {0:?}"
)]
UnauthenticatedNotesNotFound(Vec<Word>),

#[error("output note commitments already used: {0:?}")]
OutputNotesAlreadyExist(Vec<Word>),

/// The account's initial commitment did not match the current account's commitment
#[error(
"transaction's initial state commitment {tx_initial_account_commitment} does not match the account's current value of {current_account_commitment}"
)]
IncorrectAccountInitialCommitment {
tx_initial_account_commitment: Word,
current_account_commitment: Word,
},

/// Failed to retrieve transaction inputs from the store
///
/// TODO: Make this an "internal error". Q: Should we have a single `InternalError` enum for
/// all internal errors that can occur across the system?
#[error("failed to retrieve transaction inputs from the store")]
StoreConnectionFailed(#[from] StoreError),

/// Failed to verify the transaction execution proof
#[error("invalid transaction proof error for transaction: {0}")]
InvalidTransactionProof(TransactionId),
}

// Transaction adding errors
// =================================================================================================

#[derive(Debug, Error, GrpcError)]
pub enum AddTransactionError {
#[error(
"input notes with given nullifiers were already consumed by another transaction: {0:?}"
)]
InputNotesAlreadyConsumed(Vec<Nullifier>),

#[error(
"unauthenticated transaction note commitments were not found in the store or in outputs of in-flight transactions: {0:?}"
)]
UnauthenticatedNotesNotFound(Vec<Word>),

#[error("output note commitments already used: {0:?}")]
OutputNotesAlreadyExist(Vec<Word>),

#[error(
"transaction's initial state commitment {tx_initial_account_commitment} does not match the account's current value of {current_account_commitment}"
)]
IncorrectAccountInitialCommitment {
tx_initial_account_commitment: Word,
current_account_commitment: Word,
},

#[error("failed to retrieve transaction inputs from the store")]
#[grpc(internal)]
StoreConnectionFailed(#[from] StoreError),
StoreConnectionFailed(#[source] StoreError),

#[error("invalid transaction proof error for transaction: {0}")]
InvalidTransactionProof(TransactionId),
Expand All @@ -129,35 +66,31 @@ pub enum AddTransactionError {
limit: BlockNumber,
},

#[error("transaction conflicts with current mempool state")]
StateConflict(#[source] StateConflict),

#[error("the mempool is at capacity")]
CapacityExceeded,
}

impl From<VerifyTxError> for AddTransactionError {
fn from(err: VerifyTxError) -> Self {
match err {
VerifyTxError::InputNotesAlreadyConsumed(nullifiers) => {
Self::InputNotesAlreadyConsumed(nullifiers)
},
VerifyTxError::UnauthenticatedNotesNotFound(note_commitments) => {
Self::UnauthenticatedNotesNotFound(note_commitments)
},
VerifyTxError::OutputNotesAlreadyExist(note_commitments) => {
Self::OutputNotesAlreadyExist(note_commitments)
},
VerifyTxError::IncorrectAccountInitialCommitment {
tx_initial_account_commitment,
current_account_commitment,
} => Self::IncorrectAccountInitialCommitment {
tx_initial_account_commitment,
current_account_commitment,
},
VerifyTxError::StoreConnectionFailed(store_err) => {
Self::StoreConnectionFailed(store_err)
},
VerifyTxError::InvalidTransactionProof(tx_id) => Self::InvalidTransactionProof(tx_id),
}
}
// Submitted transaction conflicts with current state
// =================================================================================================
#[derive(Debug, Error, PartialEq, Eq)]
pub enum StateConflict {
#[error("nullifiers already exist: {0:?}")]
NullifiersAlreadyExist(Vec<Nullifier>),
#[error("output notes already exist: {0:?}")]
OutputNotesAlreadyExist(Vec<Word>),
#[error("unauthenticated input notes are unknown: {0:?}")]
UnauthenticatedNotesMissing(Vec<Word>),
#[error(
"initial account commitment {expected} does not match the current commitment {current} for account {account}"
)]
AccountCommitmentMismatch {
account: AccountId,
expected: Word,
current: Word,
},
}

// Submit proven batch by user errors
Expand Down
7 changes: 5 additions & 2 deletions crates/block-producer/src/mempool/budget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub struct BatchBudget {
/// Maximum number of output notes allowed.
pub output_notes: usize,
/// Maximum number of updated accounts.
///
/// Authenticated transactions are assumed to update at most one account; this field enforces
/// how many such single-account updates can fit into a batch.
pub accounts: usize,
}

Expand Down Expand Up @@ -57,7 +60,7 @@ impl BatchBudget {
/// Attempts to consume the transaction's resources from the budget.
///
/// Returns [`BudgetStatus::Exceeded`] if the transaction would exceed the remaining budget,
/// otherwise returns [`BudgetStatus::Ok`] and subtracts the resources from the budget.
/// otherwise returns [`BudgetStatus::WithinScope`] and subtracts the resources from the budget.
#[must_use]
pub(crate) fn check_then_subtract(&mut self, tx: &AuthenticatedTransaction) -> BudgetStatus {
// This type assertion reminds us to update the account check if we ever support
Expand Down Expand Up @@ -89,7 +92,7 @@ impl BlockBudget {
/// Attempts to consume the batch's resources from the budget.
///
/// Returns [`BudgetStatus::Exceeded`] if the batch would exceed the remaining budget,
/// otherwise returns [`BudgetStatus::Ok`].
/// otherwise returns [`BudgetStatus::WithinScope`].
#[must_use]
pub(crate) fn check_then_subtract(&mut self, _batch: &ProvenBatch) -> BudgetStatus {
if self.batches == 0 {
Expand Down
Loading
Loading