Skip to content
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

### Changes

- Transaction limit per batch is now configurable (default 8) (#1015).
- Batch limit per block is now configurable (default 8) (#1015).
- Separated the store API into three separate services (#932).
- Added a faucet Dockerfile (#933).
- [BREAKING] Update `RemoteProverError::ConnectionFailed` variant to contain `Error` instead of `String` (#968).
Expand Down
72 changes: 72 additions & 0 deletions bin/node/src/commands/block_producer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ impl BlockProducerCommand {
let block_producer_address =
url.to_socket().context("Failed to extract socket address from store URL")?;

// Runtime validation for protocol constraints
if block_producer.max_batches_per_block >= miden_objects::MAX_BATCHES_PER_BLOCK {
anyhow::bail!(
"max-batches-per-block cannot exceed protocol limit of {}",
miden_objects::MAX_BATCHES_PER_BLOCK
);
}
if block_producer.max_txs_per_batch >= miden_objects::MAX_ACCOUNTS_PER_BATCH {
anyhow::bail!(
"max-txs-per-batch cannot exceed protocol limit of {}",
miden_objects::MAX_ACCOUNTS_PER_BATCH
);
}

BlockProducer {
block_producer_address,
store_address,
Expand All @@ -66,6 +80,8 @@ impl BlockProducerCommand {
block_prover_url: block_producer.block_prover_url,
batch_interval: block_producer.batch_interval,
block_interval: block_producer.block_interval,
max_txs_per_batch: block_producer.max_txs_per_batch,
max_batches_per_block: block_producer.max_batches_per_block,
}
.serve()
.await
Expand All @@ -77,3 +93,59 @@ impl BlockProducerCommand {
*enable_otel
}
}

#[cfg(test)]
mod tests {
use url::Url;

use super::*;

fn dummy_url() -> Url {
Url::parse("http://127.0.0.1:1234").unwrap()
}

#[tokio::test]
async fn rejects_too_large_max_batches_per_block() {
let cmd = BlockProducerCommand::Start {
url: dummy_url(),
store_url: dummy_url(),
ntx_builder_url: None,
block_producer: BlockProducerConfig {
batch_prover_url: None,
block_prover_url: None,
block_interval: std::time::Duration::from_secs(1),
batch_interval: std::time::Duration::from_secs(1),
max_txs_per_batch: 8,
max_batches_per_block: miden_objects::MAX_BATCHES_PER_BLOCK + 1, // Invalid value
},
enable_otel: false,
};
let result = cmd.handle().await;
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("max-batches-per-block"));
}

#[tokio::test]
async fn rejects_too_large_max_txs_per_batch() {
let cmd = BlockProducerCommand::Start {
url: dummy_url(),
store_url: dummy_url(),
ntx_builder_url: None,
block_producer: BlockProducerConfig {
batch_prover_url: None,
block_prover_url: None,
block_interval: std::time::Duration::from_secs(1),
batch_interval: std::time::Duration::from_secs(1),
max_txs_per_batch: miden_objects::MAX_ACCOUNTS_PER_BATCH, /* Use protocol limit
* (should fail) */
max_batches_per_block: 8,
},
enable_otel: false,
};
let result = cmd.handle().await;
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("max-txs-per-batch"));
}
}
2 changes: 2 additions & 0 deletions bin/node/src/commands/bundled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ impl BundledCommand {
block_prover_url: block_producer.block_prover_url,
batch_interval: block_producer.batch_interval,
block_interval: block_producer.block_interval,
max_batches_per_block: block_producer.max_batches_per_block,
max_txs_per_batch: block_producer.max_txs_per_batch,
}
.serve()
.await
Expand Down
9 changes: 9 additions & 0 deletions bin/node/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::time::Duration;

use miden_node_block_producer::{SERVER_MAX_BATCHES_PER_BLOCK, SERVER_MAX_TXS_PER_BATCH};
use url::Url;

pub mod block_producer;
Expand Down Expand Up @@ -78,4 +79,12 @@ pub struct BlockProducerConfig {
/// in-process which is expensive.
#[arg(long = "block-prover.url", env = ENV_BLOCK_PROVER_URL, value_name = "URL")]
pub block_prover_url: Option<Url>,

/// The number of transactions per batch.
#[arg(long = "max-txs-per-batch", default_value_t = SERVER_MAX_TXS_PER_BATCH)]
max_txs_per_batch: usize,

/// Maximum number of batches per block.
#[arg(long = "max-batches-per-block", default_value_t = SERVER_MAX_BATCHES_PER_BLOCK)]
max_batches_per_block: usize,
}
4 changes: 2 additions & 2 deletions crates/block-producer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ pub use server::BlockProducer;
pub const COMPONENT: &str = "miden-block-producer";

/// The number of transactions per batch.
const SERVER_MAX_TXS_PER_BATCH: usize = 2;
pub const SERVER_MAX_TXS_PER_BATCH: usize = 8;

/// Maximum number of batches per block.
const SERVER_MAX_BATCHES_PER_BLOCK: usize = 4;
pub const SERVER_MAX_BATCHES_PER_BLOCK: usize = 8;

/// Size of the batch building worker pool.
const SERVER_NUM_BATCH_BUILDERS: NonZeroUsize = NonZeroUsize::new(2).unwrap();
Expand Down
10 changes: 5 additions & 5 deletions crates/block-producer/src/mempool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,20 @@ mod tests;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct BatchBudget {
/// Maximum number of transactions allowed in a batch.
transactions: usize,
pub transactions: usize,
/// Maximum number of input notes allowed.
input_notes: usize,
pub input_notes: usize,
/// Maximum number of output notes allowed.
output_notes: usize,
pub output_notes: usize,
/// Maximum number of updated accounts.
accounts: usize,
pub accounts: usize,
}

/// Limits placed on a blocks's contents.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct BlockBudget {
/// Maximum number of batches allowed in a block.
batches: usize,
pub batches: usize,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down
16 changes: 13 additions & 3 deletions crates/block-producer/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,18 @@ pub struct BlockProducer {
pub batch_interval: Duration,
/// The interval at which to produce blocks.
pub block_interval: Duration,
/// The maximum number of transactions per batch.
pub max_txs_per_batch: usize,
/// The maximum number of batches per block.
pub max_batches_per_block: usize,
}

impl BlockProducer {
/// Serves the block-producer RPC API, the batch-builder and the block-builder.
///
/// Note: Executes in place (i.e. not spawned) and will run indefinitely until
/// a fatal error is encountered.
#[allow(clippy::too_many_lines)]
pub async fn serve(self) -> anyhow::Result<()> {
info!(target: COMPONENT, endpoint=?self.block_producer_address, store=%self.store_address, "Initializing server");
let store = StoreClient::new(self.store_address);
Expand Down Expand Up @@ -120,8 +125,11 @@ impl BlockProducer {
);
let mempool = Mempool::shared(
chain_tip,
BatchBudget::default(),
BlockBudget::default(),
BatchBudget {
transactions: self.max_txs_per_batch,
..BatchBudget::default()
},
BlockBudget { batches: self.max_batches_per_block },
SERVER_MEMPOOL_STATE_RETENTION,
SERVER_MEMPOOL_EXPIRATION_SLACK,
);
Expand Down Expand Up @@ -342,7 +350,7 @@ mod test {
use tonic::transport::{Channel, Endpoint};
use winterfell::Proof;

use crate::BlockProducer;
use crate::{BlockProducer, SERVER_MAX_BATCHES_PER_BLOCK, SERVER_MAX_TXS_PER_BATCH};

#[tokio::test]
async fn block_producer_startup_is_robust_to_network_failures() {
Expand Down Expand Up @@ -380,6 +388,8 @@ mod test {
block_prover_url: None,
batch_interval: Duration::from_millis(500),
block_interval: Duration::from_millis(500),
max_txs_per_batch: SERVER_MAX_TXS_PER_BATCH,
max_batches_per_block: SERVER_MAX_BATCHES_PER_BLOCK,
}
.serve()
.await
Expand Down