Skip to content
Merged
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
- Fixed `TransactionHeader` serialization for row insertion on database & fixed transaction cursor on retrievals ([#1701](https://github.com/0xMiden/node/issues/1701)).
- Added KMS signing support in validator ([#1677](https://github.com/0xMiden/node/pull/1677)).
- Added per-IP gRPC rate limiting across services as well as global concurrent connection limit ([#1746](https://github.com/0xMiden/node/issues/1746)).
- Network transaction actors now share the same gRPC clients, limiting the number of file descriptors being used ([#1806](https://github.com/0xMiden/node/issues/1806)).
- 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)).

### Changes

Expand Down Expand Up @@ -47,6 +47,7 @@
- Fixed `bundled bootstrap` requiring `--validator.key.hex` or `--validator.key.kms-id` despite a default key being configured ([#1732](https://github.com/0xMiden/node/pull/1732)).
- Fixed incorrectly classifying private notes with the network attachment as network notes ([#1378](https://github.com/0xMiden/node/pull/1738)).
- Fixed accept header version negotiation rejecting all pre-release versions; pre-release label matching is now lenient, accepting any numeric suffix within the same label (e.g. `alpha.3` accepts `alpha.1`) ([#1755](https://github.com/0xMiden/node/pull/1755)).
- Network transaction actors now share the same gRPC clients, limiting the number of file descriptors being used ([#1806](https://github.com/0xMiden/node/issues/1806)).

## v0.13.7 (2026-02-25)

Expand Down
14 changes: 14 additions & 0 deletions bin/node/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ const ENV_VALIDATOR_KEY: &str = "MIDEN_NODE_VALIDATOR_KEY";
const ENV_VALIDATOR_KMS_KEY_ID: &str = "MIDEN_NODE_VALIDATOR_KMS_KEY_ID";
const ENV_NTX_DATA_DIRECTORY: &str = "MIDEN_NODE_NTX_DATA_DIRECTORY";
const ENV_NTX_BUILDER_URL: &str = "MIDEN_NODE_NTX_BUILDER_URL";
const ENV_NTX_MAX_CYCLES: &str = "MIDEN_NTX_MAX_CYCLES";

const DEFAULT_NTX_TICKER_INTERVAL: Duration = Duration::from_millis(200);
const DEFAULT_NTX_IDLE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
const DEFAULT_NTX_SCRIPT_CACHE_SIZE: NonZeroUsize = NonZeroUsize::new(1000).unwrap();
const DEFAULT_NTX_MAX_CYCLES: u32 = 1 << 16;

/// Configuration for the Validator key used to sign blocks.
///
Expand Down Expand Up @@ -195,6 +197,17 @@ pub struct NtxBuilderConfig {
)]
pub max_account_crashes: usize,

/// Maximum number of VM execution cycles allowed for a single network transaction.
///
/// Network transactions that exceed this limit will fail. Defaults to 2^16 (65536) cycles.
#[arg(
long = "ntx-builder.max-cycles",
env = ENV_NTX_MAX_CYCLES,
default_value_t = DEFAULT_NTX_MAX_CYCLES,
value_name = "NUM",
)]
pub max_tx_cycles: u32,

/// Directory for the ntx-builder's persistent database.
///
/// If not set, defaults to the node's data directory.
Expand Down Expand Up @@ -227,6 +240,7 @@ impl NtxBuilderConfig {
.with_script_cache_size(self.script_cache_size)
.with_idle_timeout(self.idle_timeout)
.with_max_account_crashes(self.max_account_crashes)
.with_max_cycles(self.max_tx_cycles)
}
}

Expand Down
5 changes: 5 additions & 0 deletions bin/node/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ fn bundled_bootstrap_parses() {
fn bundled_start_parses() {
let _ = parse(&["bundled", "start"]);
}

#[test]
fn bundled_start_with_max_cycles_parses() {
let _ = parse(&["bundled", "start", "--ntx-builder.max-cycles", "131072"]);
}
26 changes: 22 additions & 4 deletions crates/ntx-builder/src/actor/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use miden_tx::auth::UnreachableAuth;
use miden_tx::{
DataStore,
DataStoreError,
ExecutionOptions,
FailedNote,
LocalTransactionProver,
MastForestStore,
Expand Down Expand Up @@ -108,6 +109,9 @@ pub struct NtxContext {

/// Local database for persistent note script caching.
db: Db,

/// Maximum number of VM execution cycles for network transactions.
max_cycles: u32,
}

impl NtxContext {
Expand All @@ -119,6 +123,7 @@ impl NtxContext {
store: StoreClient,
script_cache: LruCache<Word, NoteScript>,
db: Db,
max_cycles: u32,
) -> Self {
Self {
block_producer,
Expand All @@ -127,9 +132,24 @@ impl NtxContext {
store,
script_cache,
db,
max_cycles,
}
}

/// Creates a [`TransactionExecutor`] configured with the network transaction cycle limit.
fn create_executor<'a, 'b>(
&self,
data_store: &'a NtxDataStore,
) -> TransactionExecutor<'a, 'b, NtxDataStore, UnreachableAuth> {
let exec_options =
ExecutionOptions::new(Some(self.max_cycles), self.max_cycles, false, false)
.expect("max_cycles should be within valid range");

TransactionExecutor::new(data_store)
.with_options(exec_options)
.expect("execution options should be valid for transaction executor")
}

/// Executes a transaction end-to-end: filtering, executing, proving, and submitted to the block
/// producer.
///
Expand Down Expand Up @@ -235,8 +255,7 @@ impl NtxContext {
data_store: &NtxDataStore,
notes: Vec<Note>,
) -> NtxResult<(InputNotes<InputNote>, Vec<FailedNote>)> {
let executor: TransactionExecutor<'_, '_, _, UnreachableAuth> =
TransactionExecutor::new(data_store);
let executor = self.create_executor(data_store);
let checker = NoteConsumptionChecker::new(&executor);

match Box::pin(checker.check_notes_consumability(
Expand Down Expand Up @@ -279,8 +298,7 @@ impl NtxContext {
data_store: &NtxDataStore,
notes: InputNotes<InputNote>,
) -> NtxResult<ExecutedTransaction> {
let executor: TransactionExecutor<'_, '_, _, UnreachableAuth> =
TransactionExecutor::new(data_store);
let executor = self.create_executor(data_store);

Box::pin(executor.execute_transaction(
data_store.account.id(),
Expand Down
7 changes: 7 additions & 0 deletions crates/ntx-builder/src/actor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ pub struct AccountActorContext {
pub db: Db,
/// Channel for sending requests to the coordinator (via the builder event loop).
pub request_tx: mpsc::Sender<ActorRequest>,
/// Maximum number of VM execution cycles for network transactions.
pub max_cycles: u32,
}

#[cfg(test)]
Expand Down Expand Up @@ -110,6 +112,7 @@ impl AccountActorContext {
idle_timeout: Duration::from_secs(60),
db: db.clone(),
request_tx,
max_cycles: 1 << 16,
}
}
}
Expand Down Expand Up @@ -217,6 +220,8 @@ pub struct AccountActor {
idle_timeout: Duration,
/// Channel for sending requests to the coordinator.
request_tx: mpsc::Sender<ActorRequest>,
/// Maximum number of VM execution cycles for network transactions.
max_cycles: u32,
}

impl AccountActor {
Expand All @@ -243,6 +248,7 @@ impl AccountActor {
max_note_attempts: actor_context.max_note_attempts,
idle_timeout: actor_context.idle_timeout,
request_tx: actor_context.request_tx.clone(),
max_cycles: actor_context.max_cycles,
}
}

Expand Down Expand Up @@ -390,6 +396,7 @@ impl AccountActor {
self.store.clone(),
self.script_cache.clone(),
self.db.clone(),
self.max_cycles,
);

let notes = tx_candidate.notes.clone();
Expand Down
21 changes: 21 additions & 0 deletions crates/ntx-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ const DEFAULT_IDLE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
/// Default maximum number of crashes an account actor is allowed before being deactivated.
const DEFAULT_MAX_ACCOUNT_CRASHES: usize = 10;

/// Default maximum number of VM execution cycles allowed for a network transaction.
///
/// This limits the computational cost of network transactions. The protocol maximum is
/// `1 << 29` but network transactions should be much cheaper.
const DEFAULT_MAX_TX_CYCLES: u32 = 1 << 16;

// CONFIGURATION
// =================================================================================================

Expand Down Expand Up @@ -119,6 +125,12 @@ pub struct NtxBuilderConfig {
/// Once this limit is reached, no new transactions will be created for this account.
pub max_account_crashes: usize,

/// Maximum number of VM execution cycles allowed for a single network transaction.
///
/// Network transactions that exceed this limit will fail with an execution error.
/// Defaults to 64k cycles.
pub max_cycles: u32,

/// Path to the SQLite database file used for persistent state.
pub database_filepath: PathBuf,
}
Expand All @@ -143,6 +155,7 @@ impl NtxBuilderConfig {
account_channel_capacity: DEFAULT_ACCOUNT_CHANNEL_CAPACITY,
idle_timeout: DEFAULT_IDLE_TIMEOUT,
max_account_crashes: DEFAULT_MAX_ACCOUNT_CRASHES,
max_cycles: DEFAULT_MAX_TX_CYCLES,
database_filepath,
}
}
Expand Down Expand Up @@ -224,6 +237,13 @@ impl NtxBuilderConfig {
self
}

/// Sets the maximum number of VM execution cycles for network transactions.
#[must_use]
pub fn with_max_cycles(mut self, max: u32) -> Self {
self.max_cycles = max;
self
}

/// Builds and initializes the network transaction builder.
///
/// This method connects to the store and block producer services, fetches the current
Expand Down Expand Up @@ -286,6 +306,7 @@ impl NtxBuilderConfig {
idle_timeout: self.idle_timeout,
db: db.clone(),
request_tx,
max_cycles: self.max_cycles,
};

Ok(NetworkTransactionBuilder::new(
Expand Down
Loading