Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- Added finality field for `SyncChainMmr` requests ([#1725](https://github.com/0xMiden/miden-node/pull/1725)).
- Added limit to execution cycles for a transaction network, configurable through CLI args (`--ntx-builder.max-tx-cycles`) ([#1801](https://github.com/0xMiden/node/issues/1801)).
- Added monitor version and network name to the network monitor dashboard, network name is configurable via `--network-name` / `MIDEN_MONITOR_NETWORK_NAME` ([#1838](https://github.com/0xMiden/node/pull/1838)).
- Users can now submit atomic transaction batches via `SubmitBatch` gRPC endpoint ([#1846](https://github.com/0xMiden/node/pull/1846)).

### Changes

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

8 changes: 4 additions & 4 deletions crates/block-producer/src/batch_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,12 @@ impl BatchJob {
batch: SelectedBatch,
) -> Result<(SelectedBatch, BatchInputs), BuildBatchError> {
let block_references = batch
.txs()
.transactions()
.iter()
.map(Deref::deref)
.map(AuthenticatedTransaction::reference_block);
let unauthenticated_notes = batch
.txs()
.transactions()
.iter()
.map(Deref::deref)
.flat_map(AuthenticatedTransaction::unauthenticated_note_commitments);
Expand Down Expand Up @@ -325,10 +325,10 @@ impl BatchProver {
impl TelemetryInjectorExt for SelectedBatch {
fn inject_telemetry(&self) {
Span::current().set_attribute("batch.id", self.id());
Span::current().set_attribute("transactions.count", self.txs().len());
Span::current().set_attribute("transactions.count", self.transactions().len());
// Accumulate all telemetry based on transactions.
let (tx_ids, input_notes_count, output_notes_count, unauth_notes_count) =
self.txs().iter().fold(
self.transactions().iter().fold(
(vec![], 0, 0, 0),
|(
mut tx_ids,
Expand Down
4 changes: 0 additions & 4 deletions crates/block-producer/src/domain/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ impl SelectedBatch {
self.id
}

pub(crate) fn txs(&self) -> &[Arc<AuthenticatedTransaction>] {
&self.txs
}

pub(crate) fn into_transactions(self) -> Vec<Arc<AuthenticatedTransaction>> {
self.txs
}
Expand Down
14 changes: 7 additions & 7 deletions crates/block-producer/src/domain/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl AuthenticatedTransaction {
///
/// Returns an error if any of the transaction's nullifiers are marked as spent by the inputs.
pub fn new_unchecked(
tx: ProvenTransaction,
tx: Arc<ProvenTransaction>,
inputs: TransactionInputs,
) -> Result<AuthenticatedTransaction, StateConflict> {
let nullifiers_already_spent = tx
Expand All @@ -58,7 +58,7 @@ impl AuthenticatedTransaction {
}

Ok(AuthenticatedTransaction {
inner: Arc::new(tx),
inner: tx,
notes_authenticated_by_store: inputs.found_unauthenticated_notes,
authentication_height: inputs.current_block_height,
store_account_state: inputs.account_commitment,
Expand Down Expand Up @@ -128,6 +128,10 @@ impl AuthenticatedTransaction {
pub fn expires_at(&self) -> BlockNumber {
self.inner.expiration_block_num()
}

pub fn raw_proven_transaction(&self) -> &ProvenTransaction {
&self.inner
}
}

#[cfg(test)]
Expand All @@ -151,7 +155,7 @@ impl AuthenticatedTransaction {
current_block_height: 0.into(),
};
// SAFETY: nullifiers were set to None aka are definitely unspent.
Self::new_unchecked(inner, inputs).unwrap()
Self::new_unchecked(Arc::new(inner), inputs).unwrap()
}

/// Overrides the authentication height with the given value.
Expand All @@ -171,8 +175,4 @@ impl AuthenticatedTransaction {
self.store_account_state = None;
self
}

pub fn raw_proven_transaction(&self) -> &ProvenTransaction {
&self.inner
}
}
23 changes: 7 additions & 16 deletions crates/block-producer/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ pub enum BlockProducerError {
},
}

// Transaction adding errors
// Add transaction and add user batch errors
// =================================================================================================

#[derive(Debug, Error, GrpcError)]
pub enum AddTransactionError {
#[error("failed to retrieve transaction inputs from the store")]
pub enum MempoolSubmissionError {
#[error("failed to retrieve inputs from the store")]
#[grpc(internal)]
StoreConnectionFailed(#[source] StoreError),

Expand All @@ -56,8 +56,8 @@ pub enum AddTransactionError {
stale_limit: BlockNumber,
},

#[error("transaction deserialization failed")]
TransactionDeserializationFailed(#[source] DeserializationError),
#[error("request deserialization failed")]
DeserializationFailed(#[source] DeserializationError),

#[error(
"transaction expired at block height {expired_at} but the block height limit was {limit}"
Expand All @@ -74,8 +74,9 @@ pub enum AddTransactionError {
CapacityExceeded,
}

// Submitted transaction conflicts with current state
// Mempool submission conflicts with current state
// =================================================================================================

#[derive(Debug, Error, PartialEq, Eq)]
pub enum StateConflict {
#[error("nullifiers already exist: {0:?}")]
Expand All @@ -94,16 +95,6 @@ pub enum StateConflict {
},
}

// Submit proven batch by user errors
// =================================================================================================

#[derive(Debug, Error, GrpcError)]
#[grpc(internal)]
pub enum SubmitProvenBatchError {
#[error("batch deserialization failed")]
Deserialization(#[source] DeserializationError),
}

// Batch building errors
// =================================================================================================

Expand Down
22 changes: 13 additions & 9 deletions crates/block-producer/src/mempool/graph/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ impl GraphNode for SelectedBatch {
type Id = BatchId;

fn nullifiers(&self) -> Box<dyn Iterator<Item = Nullifier> + '_> {
Box::new(self.txs().iter().flat_map(|tx| tx.nullifiers()))
Box::new(self.transactions().iter().flat_map(|tx| tx.nullifiers()))
}

fn output_notes(&self) -> Box<dyn Iterator<Item = Word> + '_> {
Box::new(self.txs().iter().flat_map(|tx| tx.output_note_commitments()))
Box::new(self.transactions().iter().flat_map(|tx| tx.output_note_commitments()))
}

fn unauthenticated_notes(&self) -> Box<dyn Iterator<Item = Word> + '_> {
Box::new(self.txs().iter().flat_map(|tx| tx.unauthenticated_note_commitments()))
Box::new(self.transactions().iter().flat_map(|tx| tx.unauthenticated_note_commitments()))
}

fn account_updates(
Expand Down Expand Up @@ -98,10 +98,14 @@ impl BatchGraph {
///
/// Batches are returned in reverse-chronological order.
pub fn revert_expired(&mut self, chain_tip: BlockNumber) -> Vec<SelectedBatch> {
let reverted = self.inner.revert_expired_unselected(chain_tip);
// We only revert transactions which are _not_ included in batches.
let mut to_revert = self.inner.expired(chain_tip);
to_revert.retain(|batch| !self.inner.is_selected(batch));

for batch in &reverted {
self.proven.remove(&batch.id());
let mut reverted = Vec::with_capacity(to_revert.len());

for batch in to_revert {
reverted.extend_from_slice(&self.revert_batch_and_descendants(batch));
}

reverted
Expand Down Expand Up @@ -144,15 +148,15 @@ impl BatchGraph {
selected
}

/// Prunes the given batch.
/// Prunes the given batch and returns it.
///
/// # Panics
///
/// Panics if the batch does not exist, or has existing ancestors in the batch
/// graph.
pub fn prune(&mut self, batch: BatchId) {
self.inner.prune(batch);
pub fn prune(&mut self, batch: BatchId) -> SelectedBatch {
self.proven.remove(&batch);
self.inner.prune(batch)
}

pub fn proven_count(&self) -> usize {
Expand Down
55 changes: 7 additions & 48 deletions crates/block-producer/src/mempool/graph/dag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,48 +199,33 @@ where
reverted
}

/// Reverts nodes (and their descendants) which have expired and which are _not_ selected.
///
/// Returns the reverted nodes in **reverse** chronological order.
pub fn revert_expired_unselected(&mut self, chain_tip: BlockNumber) -> Vec<N> {
let mut reverted = Vec::default();

let expired = self
.nodes
/// Returns the set of nodes that expired at the given block height.
pub fn expired(&self, chain_tip: BlockNumber) -> HashSet<N::Id> {
self.nodes
.iter()
.filter(|(id, _)| !self.is_selected(id))
.filter_map(|(id, node)| (node.expires_at() <= chain_tip).then_some(id))
.copied()
.collect::<HashSet<_>>();

for id in expired {
// Its possible the node is already reverted by a previous loop iteration.
if self.contains(&id) {
reverted.extend(self.revert_node_and_descendants(id));
}
}

reverted
.collect()
}

/// Returns `true` if the given node is a leaf node aka has no children.
fn is_leaf(&self, id: &N::Id) -> bool {
self.edges.children_of(id).is_empty()
}

/// Removes the node _IFF_ it has no ancestor nodes.
/// Removes the node _IFF_ it has no ancestor nodes and returns the pruned node.
///
/// # Panics
///
/// Panics if this node has any ancestor nodes, or if this node was not selected.
pub fn prune(&mut self, id: N::Id) {
pub fn prune(&mut self, id: N::Id) -> N {
assert!(
self.edges.parents_of(&id).is_empty(),
"Cannot prune node {id} as it still has ancestors",
);
assert!(self.selected.contains(&id), "Cannot prune node {id} as it was not selected");

self.remove(id);
self.remove(id)
}

/// Unconditionally removes the given node from the graph, deleting its edges and state.
Expand Down Expand Up @@ -331,30 +316,4 @@ mod tests {
graph.selection_candidates().keys().map(|id| **id).collect();
assert_eq!(candidates_after_parent, vec![2]);
}

#[test]
fn revert_expired_unselected_removes_descendants() {
let mut graph = Graph::<TestNode>::default();

graph
.append(
TestNode::new(1).with_output_notes([1]).with_expires_at(BlockNumber::from(2u32)),
)
.unwrap();
graph
.append(
TestNode::new(2)
.with_output_notes([2])
.with_unauthenticated_notes([1])
.with_expires_at(BlockNumber::from(3u32)),
)
.unwrap();

let reverted = graph.revert_expired_unselected(BlockNumber::from(3u32));
let reverted_ids: Vec<u32> = reverted.into_iter().map(|node| node.id).collect();

assert_eq!(reverted_ids, vec![2, 1]);
assert_eq!(graph.node_count(), 0);
assert_eq!(graph.selection_candidates().len(), 0);
}
}
Loading
Loading