From 5190a34cee4bcee8e8dd308e52eb7c0570942e31 Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 6 May 2026 12:24:53 +1200 Subject: [PATCH 01/16] Add bin/validator crate --- Cargo.lock | 17 +- Cargo.toml | 1 + bin/node/Cargo.toml | 1 - bin/node/src/commands/mod.rs | 1 - bin/node/src/main.rs | 6 - bin/node/src/tests.rs | 10 - bin/validator/Cargo.toml | 27 ++ bin/validator/Dockerfile | 54 ++++ .../validator.rs => validator/src/main.rs} | 267 +++++++++--------- 9 files changed, 237 insertions(+), 147 deletions(-) create mode 100644 bin/validator/Cargo.toml create mode 100644 bin/validator/Dockerfile rename bin/{node/src/commands/validator.rs => validator/src/main.rs} (68%) diff --git a/Cargo.lock b/Cargo.lock index 4cf663324a..0e7b18daa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3213,7 +3213,6 @@ dependencies = [ "miden-node-rpc", "miden-node-store", "miden-node-utils", - "miden-node-validator", "miden-protocol", "tokio", "url", @@ -3797,6 +3796,22 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "miden-validator" +version = "0.15.0" +dependencies = [ + "anyhow", + "clap", + "fs-err", + "hex", + "miden-node-store", + "miden-node-utils", + "miden-node-validator", + "miden-protocol", + "tokio", + "url", +] + [[package]] name = "miden-verifier" version = "0.22.1" diff --git a/Cargo.toml b/Cargo.toml index 268a5687dd..b90adbbf98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "bin/node", "bin/remote-prover", "bin/stress-test", + "bin/validator", "crates/block-producer", "crates/db", "crates/grpc-error-macro", diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index fd242c4386..4968bd9a2c 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -28,7 +28,6 @@ miden-node-ntx-builder = { workspace = true } miden-node-rpc = { workspace = true } miden-node-store = { workspace = true } miden-node-utils = { workspace = true } -miden-node-validator = { workspace = true } miden-protocol = { workspace = true } tokio = { features = ["macros", "net", "rt-multi-thread"], workspace = true } url = { workspace = true } diff --git a/bin/node/src/commands/mod.rs b/bin/node/src/commands/mod.rs index 876e19af55..95f7e14b65 100644 --- a/bin/node/src/commands/mod.rs +++ b/bin/node/src/commands/mod.rs @@ -2,7 +2,6 @@ pub mod block_producer; pub mod ntx_builder; pub mod rpc; pub mod store; -pub mod validator; const ENV_DATA_DIRECTORY: &str = "MIDEN_NODE_DATA_DIRECTORY"; const ENV_ENABLE_OTEL: &str = "MIDEN_NODE_ENABLE_OTEL"; diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index 93789b97e9..f35414d22b 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -33,10 +33,6 @@ pub enum Command { #[command(subcommand)] BlockProducer(commands::block_producer::BlockProducerCommand), - /// Commands related to the node's validator component. - #[command(subcommand)] - Validator(commands::validator::ValidatorCommand), - /// Commands related to the node's network transaction builder component. #[command(subcommand)] NtxBuilder(commands::ntx_builder::NtxBuilderCommand), @@ -51,7 +47,6 @@ impl Command { Command::Store(subcommand) => subcommand.is_open_telemetry_enabled(), Command::Rpc(subcommand) => subcommand.is_open_telemetry_enabled(), Command::BlockProducer(subcommand) => subcommand.is_open_telemetry_enabled(), - Command::Validator(subcommand) => subcommand.is_open_telemetry_enabled(), Command::NtxBuilder(subcommand) => subcommand.is_open_telemetry_enabled(), } { OpenTelemetry::Enabled @@ -65,7 +60,6 @@ impl Command { Command::Rpc(rpc_command) => rpc_command.handle().await, Command::Store(store_command) => store_command.handle().await, Command::BlockProducer(block_producer_command) => block_producer_command.handle().await, - Command::Validator(validator) => validator.handle().await, Command::NtxBuilder(ntx_builder) => ntx_builder.handle().await, } } diff --git a/bin/node/src/tests.rs b/bin/node/src/tests.rs index d5e1b00570..7b6e3f5e30 100644 --- a/bin/node/src/tests.rs +++ b/bin/node/src/tests.rs @@ -16,16 +16,6 @@ fn block_producer_start_parses() { let _ = parse(&["block-producer", "start"]); } -#[test] -fn validator_bootstrap_parses() { - let _ = parse(&["validator", "bootstrap"]); -} - -#[test] -fn validator_start_parses() { - let _ = parse(&["validator", "start"]); -} - #[test] fn bundled_bootstrap_parses() { let _ = parse(&["bundled", "bootstrap"]); diff --git a/bin/validator/Cargo.toml b/bin/validator/Cargo.toml new file mode 100644 index 0000000000..3d4d5c1b32 --- /dev/null +++ b/bin/validator/Cargo.toml @@ -0,0 +1,27 @@ +[package] +authors.workspace = true +description = "Miden validator binary" +edition.workspace = true +homepage.workspace = true +keywords = ["miden", "validator"] +license.workspace = true +name = "miden-validator" +readme.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[lints] +workspace = true + +[dependencies] +anyhow = { workspace = true } +clap = { features = ["env", "string"], workspace = true } +fs-err = { workspace = true } +hex = { workspace = true } +miden-node-store = { workspace = true } +miden-node-utils = { workspace = true } +miden-node-validator = { workspace = true } +miden-protocol = { workspace = true } +tokio = { features = ["macros", "rt-multi-thread"], workspace = true } +url = { workspace = true } diff --git a/bin/validator/Dockerfile b/bin/validator/Dockerfile new file mode 100644 index 0000000000..887463f1fd --- /dev/null +++ b/bin/validator/Dockerfile @@ -0,0 +1,54 @@ +FROM rust:1.93-slim-bookworm AS chef +# Install build dependencies. RocksDB is compiled from source by librocksdb-sys. +RUN apt-get update && \ + apt-get -y upgrade && \ + apt-get install -y \ + llvm \ + clang \ + libclang-dev \ + cmake \ + pkg-config \ + libssl-dev \ + libsqlite3-dev \ + ca-certificates && \ + rm -rf /var/lib/apt/lists/* +RUN cargo install cargo-chef +WORKDIR /app + +FROM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +FROM chef AS builder +COPY --from=planner /app/recipe.json recipe.json +# Build dependencies - this is the caching Docker layer! +RUN cargo chef cook --release --recipe-path recipe.json +# Build application +COPY . . +RUN cargo build --release --locked --bin miden-validator + +# Base line runtime image with runtime dependencies installed. +FROM debian:bookworm-slim AS runtime-base +RUN apt-get update && \ + apt-get -y upgrade && \ + apt-get install -y --no-install-recommends sqlite3 ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +FROM runtime-base AS runtime +COPY --from=builder /app/target/release/miden-validator /usr/local/bin/miden-validator +LABEL org.opencontainers.image.authors=devops@miden.team \ + org.opencontainers.image.url=https://0xMiden.github.io/ \ + org.opencontainers.image.documentation=https://github.com/0xMiden/node \ + org.opencontainers.image.source=https://github.com/0xMiden/node \ + org.opencontainers.image.vendor=Miden \ + org.opencontainers.image.licenses=MIT +ARG CREATED +ARG VERSION +ARG COMMIT +LABEL org.opencontainers.image.created=$CREATED \ + org.opencontainers.image.version=$VERSION \ + org.opencontainers.image.revision=$COMMIT + +# Validator port is configurable via --url; 50101 is the default used in docker-compose. +EXPOSE 50101 +CMD ["miden-validator"] diff --git a/bin/node/src/commands/validator.rs b/bin/validator/src/main.rs similarity index 68% rename from bin/node/src/commands/validator.rs rename to bin/validator/src/main.rs index 807f5fa129..2e0552eaff 100644 --- a/bin/node/src/commands/validator.rs +++ b/bin/validator/src/main.rs @@ -2,18 +2,20 @@ use std::net::SocketAddr; use std::path::{Path, PathBuf}; use anyhow::Context; +use clap::{Parser, Subcommand}; use miden_node_store::genesis::config::{AccountFileWithName, GenesisConfig}; use miden_node_utils::clap::GrpcOptionsInternal; use miden_node_utils::fs::ensure_empty_directory; use miden_node_utils::grpc::UrlExt; +use miden_node_utils::logging::OpenTelemetry; use miden_node_utils::signer::BlockSigner; use miden_node_validator::{Validator, ValidatorSigner}; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; use miden_protocol::utils::serde::{Deserializable, Serializable}; use url::Url; -use crate::commands::{ENV_DATA_DIRECTORY, ENV_ENABLE_OTEL}; - +const ENV_DATA_DIRECTORY: &str = "MIDEN_NODE_DATA_DIRECTORY"; +const ENV_ENABLE_OTEL: &str = "MIDEN_NODE_ENABLE_OTEL"; const ENV_URL: &str = "MIDEN_NODE_VALIDATOR_URL"; const ENV_GENESIS_CONFIG_FILE: &str = "MIDEN_NODE_VALIDATOR_GENESIS_CONFIG_FILE"; const ENV_KEY: &str = "MIDEN_NODE_VALIDATOR_KEY"; @@ -22,13 +24,33 @@ const ENV_KMS_KEY_ID: &str = "MIDEN_NODE_VALIDATOR_KMS_KEY_ID"; /// A predefined, insecure validator key for development purposes. const INSECURE_KEY_HEX: &str = "0101010101010101010101010101010101010101010101010101010101010101"; /// The filename used for the genesis block file. -pub const GENESIS_BLOCK_FILENAME: &str = "genesis.dat"; +const GENESIS_BLOCK_FILENAME: &str = "genesis.dat"; -// VALIDATOR COMMAND +// CLI // ================================================================================================ -#[derive(clap::Subcommand)] -pub enum ValidatorCommand { +#[derive(Parser)] +#[command(version, about, long_about = None)] +struct Cli { + #[command(subcommand)] + command: ValidatorCommand, +} + +impl Cli { + fn open_telemetry(&self) -> OpenTelemetry { + if self.command.is_open_telemetry_enabled() { + OpenTelemetry::Enabled + } else { + OpenTelemetry::Disabled + } + } +} + +// COMMANDS +// ================================================================================================ + +#[derive(Subcommand)] +enum ValidatorCommand { /// Bootstraps the genesis block. /// /// Creates accounts from the genesis configuration, builds and signs the genesis block, @@ -100,8 +122,7 @@ pub enum ValidatorCommand { } impl ValidatorCommand { - /// Runs the validator command. - pub async fn handle(self) -> anyhow::Result<()> { + async fn handle(self) -> anyhow::Result<()> { match self { Self::Bootstrap { genesis_block_directory, @@ -110,7 +131,7 @@ impl ValidatorCommand { genesis_config_file, validator_key, } => { - Self::bootstrap_genesis( + bootstrap_genesis( &genesis_block_directory, &accounts_directory, &data_directory, @@ -131,95 +152,82 @@ impl ValidatorCommand { .to_socket() .context("failed to extract socket address from validator URL")?; - // Run validator with KMS key backend if key id provided. if let Some(kms_key_id) = kms_key_id { let signer = ValidatorSigner::new_kms(kms_key_id).await?; - Self::serve(address, grpc_options, signer, data_directory).await + serve(address, grpc_options, signer, data_directory).await } else { let signer = SecretKey::read_from_bytes(hex::decode(validator_key)?.as_ref())?; let signer = ValidatorSigner::new_local(signer); - Self::serve(address, grpc_options, signer, data_directory).await + serve(address, grpc_options, signer, data_directory).await } }, } } - /// Runs the validator component until failure. - async fn serve( - address: SocketAddr, - grpc_options: GrpcOptionsInternal, - signer: ValidatorSigner, - data_directory: PathBuf, - ) -> anyhow::Result<()> { - Validator { - address, - grpc_options, - signer, - data_directory, - } - .serve() - .await - .context("failed while serving validator component") - } - - pub fn is_open_telemetry_enabled(&self) -> bool { + fn is_open_telemetry_enabled(&self) -> bool { match self { Self::Start { enable_otel, .. } => *enable_otel, Self::Bootstrap { .. } => false, } } +} - /// Bootstraps the genesis block: creates accounts, signs the block, and writes artifacts to - /// disk. - async fn bootstrap_genesis( - genesis_block_directory: &Path, - accounts_directory: &Path, - data_directory: &Path, - genesis_config: Option<&PathBuf>, - validator_key: ValidatorKey, - ) -> anyhow::Result<()> { - // Parse genesis config (or default if not given). - let config = genesis_config - .map(|file_path| { - GenesisConfig::read_toml_file(file_path).with_context(|| { - format!("failed to parse genesis config from file {}", file_path.display()) - }) - }) - .transpose()? - .unwrap_or_default(); +// VALIDATOR KEY +// ================================================================================================ - // Create directories if they do not already exist. - for directory in [accounts_directory, genesis_block_directory] { - ensure_empty_directory(directory)?; - } +/// Configuration for the Validator key used to sign blocks. +#[derive(clap::Args)] +#[group(required = false, multiple = false)] +struct ValidatorKey { + /// Insecure, hex-encoded validator secret key for development and testing purposes. + /// + /// If not provided, a predefined key is used. + /// + /// Cannot be used with `validator.key.kms-id`. + #[arg( + long = "validator.key.hex", + env = ENV_KEY, + value_name = "VALIDATOR_KEY", + default_value = INSECURE_KEY_HEX, + )] + validator_key: String, + /// Key ID for the KMS key used by validator to sign blocks. + /// + /// Cannot be used with `validator.key.hex`. + #[arg( + long = "validator.key.kms-id", + env = ENV_KMS_KEY_ID, + value_name = "VALIDATOR_KMS_KEY_ID", + )] + validator_kms_key_id: Option, +} - // Bootstrap with KMS key or local key. - let signer = validator_key.into_signer().await?; - match signer { - ValidatorSigner::Kms(signer) => { - build_and_write_genesis( - config, - signer, - accounts_directory, - genesis_block_directory, - data_directory, - ) - .await - }, - ValidatorSigner::Local(signer) => { - build_and_write_genesis( - config, - signer, - accounts_directory, - genesis_block_directory, - data_directory, - ) - .await - }, +impl ValidatorKey { + async fn into_signer(self) -> anyhow::Result { + if let Some(kms_key_id) = self.validator_kms_key_id { + Ok(ValidatorSigner::new_kms(kms_key_id).await?) + } else { + let signer = SecretKey::read_from_bytes(hex::decode(self.validator_key)?.as_ref())?; + Ok(ValidatorSigner::new_local(signer)) } } } +// HELPERS +// ================================================================================================ + +async fn serve( + address: SocketAddr, + grpc_options: GrpcOptionsInternal, + signer: ValidatorSigner, + data_directory: PathBuf, +) -> anyhow::Result<()> { + Validator { address, grpc_options, signer, data_directory } + .serve() + .await + .context("failed while serving validator component") +} + /// Builds the genesis state, writes account secret files, signs the genesis block, writes it /// to disk, and initializes the validator's database with the genesis block as the chain tip. async fn build_and_write_genesis( @@ -229,32 +237,27 @@ async fn build_and_write_genesis( genesis_block_directory: &Path, data_directory: &Path, ) -> anyhow::Result<()> { - // Build genesis state with the provided signer. let (genesis_state, secrets) = config.into_state(signer)?; - // Write account secret files. for item in secrets.as_account_files(&genesis_state) { let AccountFileWithName { account_file, name } = item?; - let accountpath = accounts_directory.join(name); + let account_path = accounts_directory.join(name); // Do not override existing keys. fs_err::OpenOptions::new() .create_new(true) .write(true) - .open(&accountpath) + .open(&account_path) .context("key file already exists")?; - account_file.write(accountpath)?; + account_file.write(account_path)?; } - // Build the signed genesis block. let genesis_block = genesis_state.into_block().await.context("failed to build the genesis block")?; - // Serialize and write the genesis block to disk. let block_bytes = genesis_block.inner().to_bytes(); let genesis_block_path = genesis_block_directory.join(GENESIS_BLOCK_FILENAME); fs_err::write(&genesis_block_path, block_bytes).context("failed to write genesis block")?; - // Initialize the validator database and persist the genesis block header as the chain tip. let (genesis_header, ..) = genesis_block.into_inner().into_parts(); let db = miden_node_validator::db::load(data_directory.join("validator.sqlite3")) .await @@ -268,51 +271,59 @@ async fn build_and_write_genesis( Ok(()) } -// VALIDATOR KEY -// ================================================================================================ +async fn bootstrap_genesis( + genesis_block_directory: &Path, + accounts_directory: &Path, + data_directory: &Path, + genesis_config: Option<&PathBuf>, + validator_key: ValidatorKey, +) -> anyhow::Result<()> { + let config = genesis_config + .map(|file_path| { + GenesisConfig::read_toml_file(file_path).with_context(|| { + format!("failed to parse genesis config from file {}", file_path.display()) + }) + }) + .transpose()? + .unwrap_or_default(); -/// Configuration for the Validator key used to sign blocks. -/// -/// Used by the Validator command and the genesis bootstrap command. -#[derive(clap::Args)] -#[group(required = false, multiple = false)] -pub struct ValidatorKey { - /// Insecure, hex-encoded validator secret key for development and testing purposes. - /// - /// If not provided, a predefined key is used. - /// - /// Cannot be used with `validator.key.kms-id`. - #[arg( - long = "validator.key.hex", - env = ENV_KEY, - value_name = "VALIDATOR_KEY", - default_value = INSECURE_KEY_HEX, - )] - validator_key: String, - /// Key ID for the KMS key used by validator to sign blocks. - /// - /// Cannot be used with `validator.key.hex`. - #[arg( - long = "validator.key.kms-id", - env = ENV_KMS_KEY_ID, - value_name = "VALIDATOR_KMS_KEY_ID", - )] - validator_kms_key_id: Option, -} + for directory in [accounts_directory, genesis_block_directory] { + ensure_empty_directory(directory)?; + } -impl ValidatorKey { - /// Consumes the validator key configuration and returns a KMS or local key signer depending on - /// the supplied configuration. - pub async fn into_signer(self) -> anyhow::Result { - if let Some(kms_key_id) = self.validator_kms_key_id { - // Use KMS key ID to create a ValidatorSigner. - let signer = ValidatorSigner::new_kms(kms_key_id).await?; - Ok(signer) - } else { - // Use hex-encoded key to create a ValidatorSigner. - let signer = SecretKey::read_from_bytes(hex::decode(self.validator_key)?.as_ref())?; - let signer = ValidatorSigner::new_local(signer); - Ok(signer) - } + let signer = validator_key.into_signer().await?; + match signer { + ValidatorSigner::Kms(signer) => { + build_and_write_genesis( + config, + signer, + accounts_directory, + genesis_block_directory, + data_directory, + ) + .await + }, + ValidatorSigner::Local(signer) => { + build_and_write_genesis( + config, + signer, + accounts_directory, + genesis_block_directory, + data_directory, + ) + .await + }, } } + +// MAIN +// ================================================================================================ + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + + let _otel_guard = miden_node_utils::logging::setup_tracing(cli.open_telemetry())?; + + cli.command.handle().await +} From 935377ee75f792e3d58eea20adc60807fc917e2e Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 6 May 2026 12:31:55 +1200 Subject: [PATCH 02/16] Split module up --- bin/validator/src/commands/bootstrap.rs | 86 +++++++ bin/validator/src/commands/mod.rs | 191 +++++++++++++++ bin/validator/src/commands/start.rs | 18 ++ bin/validator/src/main.rs | 299 +----------------------- 4 files changed, 298 insertions(+), 296 deletions(-) create mode 100644 bin/validator/src/commands/bootstrap.rs create mode 100644 bin/validator/src/commands/mod.rs create mode 100644 bin/validator/src/commands/start.rs diff --git a/bin/validator/src/commands/bootstrap.rs b/bin/validator/src/commands/bootstrap.rs new file mode 100644 index 0000000000..2c89ee2342 --- /dev/null +++ b/bin/validator/src/commands/bootstrap.rs @@ -0,0 +1,86 @@ +use std::path::{Path, PathBuf}; + +use anyhow::Context; +use miden_node_store::genesis::config::{AccountFileWithName, GenesisConfig}; +use miden_node_utils::fs::ensure_empty_directory; +use miden_node_utils::signer::BlockSigner; +use miden_node_validator::ValidatorSigner; +use miden_protocol::utils::serde::Serializable; + +use super::ValidatorKey; + +const GENESIS_BLOCK_FILENAME: &str = "genesis.dat"; + +pub async fn run( + genesis_block_directory: &Path, + accounts_directory: &Path, + data_directory: &Path, + genesis_config: Option<&PathBuf>, + validator_key: ValidatorKey, +) -> anyhow::Result<()> { + let config = genesis_config + .map(|file_path| { + GenesisConfig::read_toml_file(file_path).with_context(|| { + format!("failed to parse genesis config from file {}", file_path.display()) + }) + }) + .transpose()? + .unwrap_or_default(); + + for directory in [accounts_directory, genesis_block_directory] { + ensure_empty_directory(directory)?; + } + + let signer = validator_key.into_signer().await?; + match signer { + ValidatorSigner::Kms(signer) => { + build_and_write_genesis(config, signer, accounts_directory, genesis_block_directory, data_directory).await + }, + ValidatorSigner::Local(signer) => { + build_and_write_genesis(config, signer, accounts_directory, genesis_block_directory, data_directory).await + }, + } +} + +/// Builds the genesis state, writes account secret files, signs the genesis block, writes it +/// to disk, and initializes the validator's database with the genesis block as the chain tip. +async fn build_and_write_genesis( + config: GenesisConfig, + signer: impl BlockSigner, + accounts_directory: &Path, + genesis_block_directory: &Path, + data_directory: &Path, +) -> anyhow::Result<()> { + let (genesis_state, secrets) = config.into_state(signer)?; + + for item in secrets.as_account_files(&genesis_state) { + let AccountFileWithName { account_file, name } = item?; + let account_path = accounts_directory.join(name); + // Do not override existing keys. + fs_err::OpenOptions::new() + .create_new(true) + .write(true) + .open(&account_path) + .context("key file already exists")?; + account_file.write(account_path)?; + } + + let genesis_block = + genesis_state.into_block().await.context("failed to build the genesis block")?; + + let block_bytes = genesis_block.inner().to_bytes(); + let genesis_block_path = genesis_block_directory.join(GENESIS_BLOCK_FILENAME); + fs_err::write(&genesis_block_path, block_bytes).context("failed to write genesis block")?; + + let (genesis_header, ..) = genesis_block.into_inner().into_parts(); + let db = miden_node_validator::db::load(data_directory.join("validator.sqlite3")) + .await + .context("failed to initialize validator database during bootstrap")?; + db.transact("upsert_block_header", move |conn| { + miden_node_validator::db::upsert_block_header(conn, &genesis_header) + }) + .await + .context("failed to persist genesis block header as chain tip")?; + + Ok(()) +} diff --git a/bin/validator/src/commands/mod.rs b/bin/validator/src/commands/mod.rs new file mode 100644 index 0000000000..da56a7757b --- /dev/null +++ b/bin/validator/src/commands/mod.rs @@ -0,0 +1,191 @@ +mod bootstrap; +mod start; + +use std::path::PathBuf; + +use anyhow::Context; +use clap::Subcommand; +use miden_node_utils::clap::GrpcOptionsInternal; +use miden_node_utils::grpc::UrlExt; +use miden_node_validator::ValidatorSigner; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; +use miden_protocol::utils::serde::Deserializable; +use url::Url; + +pub(crate) const ENV_DATA_DIRECTORY: &str = "MIDEN_NODE_DATA_DIRECTORY"; +const ENV_ENABLE_OTEL: &str = "MIDEN_NODE_ENABLE_OTEL"; +const ENV_URL: &str = "MIDEN_NODE_VALIDATOR_URL"; +const ENV_GENESIS_CONFIG_FILE: &str = "MIDEN_NODE_VALIDATOR_GENESIS_CONFIG_FILE"; +pub(crate) const ENV_KEY: &str = "MIDEN_NODE_VALIDATOR_KEY"; +pub(crate) const ENV_KMS_KEY_ID: &str = "MIDEN_NODE_VALIDATOR_KMS_KEY_ID"; + +/// A predefined, insecure validator key for development purposes. +pub(crate) const INSECURE_KEY_HEX: &str = + "0101010101010101010101010101010101010101010101010101010101010101"; + +// VALIDATOR COMMAND +// ================================================================================================ + +#[derive(Subcommand)] +pub enum ValidatorCommand { + /// Bootstraps the genesis block. + /// + /// Creates accounts from the genesis configuration, builds and signs the genesis block, + /// and writes the signed block and account secret files to disk. Also initializes the + /// validator's database with the genesis block as the chain tip. + Bootstrap { + /// Directory in which to write the genesis block file. + #[arg(long, value_name = "DIR")] + genesis_block_directory: PathBuf, + /// Directory to write the account secret files (.mac) to. + #[arg(long, value_name = "DIR")] + accounts_directory: PathBuf, + /// Directory in which to store the validator's database. + #[arg(long, env = ENV_DATA_DIRECTORY, value_name = "DIR")] + data_directory: PathBuf, + /// Use the given configuration file to construct the genesis state from. + #[arg(long, env = ENV_GENESIS_CONFIG_FILE, value_name = "GENESIS_CONFIG")] + genesis_config_file: Option, + /// Configuration for the Validator key used to sign the genesis block. + #[command(flatten)] + validator_key: ValidatorKey, + }, + + /// Starts the validator component. + Start { + /// Url at which to serve the gRPC API. + #[arg(env = ENV_URL)] + url: Url, + + /// Enables the exporting of traces for OpenTelemetry. + /// + /// This can be further configured using environment variables as defined in the official + /// OpenTelemetry documentation. See our operator manual for further details. + #[arg(long = "enable-otel", default_value_t = false, env = ENV_ENABLE_OTEL, value_name = "BOOL")] + enable_otel: bool, + + #[command(flatten)] + grpc_options: GrpcOptionsInternal, + + /// Directory in which to store the validator's data. + #[arg(long, env = ENV_DATA_DIRECTORY, value_name = "DIR")] + data_directory: PathBuf, + + /// Insecure, hex-encoded validator secret key for development and testing purposes. + /// + /// If not provided, a predefined key is used. + /// + /// Cannot be used with `key.kms-id`. + #[arg( + long = "key.hex", + env = ENV_KEY, + value_name = "VALIDATOR_KEY", + default_value = INSECURE_KEY_HEX, + group = "key" + )] + validator_key: String, + + /// Key ID for the KMS key used by validator to sign blocks. + /// + /// Cannot be used with `key.hex`. + #[arg( + long = "key.kms-id", + env = ENV_KMS_KEY_ID, + value_name = "VALIDATOR_KMS_KEY_ID", + group = "key" + )] + kms_key_id: Option, + }, +} + +impl ValidatorCommand { + pub async fn handle(self) -> anyhow::Result<()> { + match self { + Self::Bootstrap { + genesis_block_directory, + accounts_directory, + data_directory, + genesis_config_file, + validator_key, + } => { + bootstrap::run( + &genesis_block_directory, + &accounts_directory, + &data_directory, + genesis_config_file.as_ref(), + validator_key, + ) + .await + }, + Self::Start { + url, + grpc_options, + validator_key, + data_directory, + kms_key_id, + .. + } => { + let address = url + .to_socket() + .context("failed to extract socket address from validator URL")?; + + if let Some(kms_key_id) = kms_key_id { + let signer = ValidatorSigner::new_kms(kms_key_id).await?; + start::run(address, grpc_options, signer, data_directory).await + } else { + let signer = SecretKey::read_from_bytes(hex::decode(validator_key)?.as_ref())?; + let signer = ValidatorSigner::new_local(signer); + start::run(address, grpc_options, signer, data_directory).await + } + }, + } + } + + pub fn is_open_telemetry_enabled(&self) -> bool { + match self { + Self::Start { enable_otel, .. } => *enable_otel, + Self::Bootstrap { .. } => false, + } + } +} + +// VALIDATOR KEY +// ================================================================================================ + +/// Configuration for the Validator key used to sign blocks. +#[derive(clap::Args)] +#[group(required = false, multiple = false)] +pub struct ValidatorKey { + /// Insecure, hex-encoded validator secret key for development and testing purposes. + /// + /// If not provided, a predefined key is used. + /// + /// Cannot be used with `validator.key.kms-id`. + #[arg( + long = "validator.key.hex", + env = ENV_KEY, + value_name = "VALIDATOR_KEY", + default_value = INSECURE_KEY_HEX, + )] + pub validator_key: String, + /// Key ID for the KMS key used by validator to sign blocks. + /// + /// Cannot be used with `validator.key.hex`. + #[arg( + long = "validator.key.kms-id", + env = ENV_KMS_KEY_ID, + value_name = "VALIDATOR_KMS_KEY_ID", + )] + pub validator_kms_key_id: Option, +} + +impl ValidatorKey { + pub async fn into_signer(self) -> anyhow::Result { + if let Some(kms_key_id) = self.validator_kms_key_id { + Ok(ValidatorSigner::new_kms(kms_key_id).await?) + } else { + let signer = SecretKey::read_from_bytes(hex::decode(self.validator_key)?.as_ref())?; + Ok(ValidatorSigner::new_local(signer)) + } + } +} diff --git a/bin/validator/src/commands/start.rs b/bin/validator/src/commands/start.rs new file mode 100644 index 0000000000..1bea5470ef --- /dev/null +++ b/bin/validator/src/commands/start.rs @@ -0,0 +1,18 @@ +use std::net::SocketAddr; +use std::path::PathBuf; + +use anyhow::Context; +use miden_node_utils::clap::GrpcOptionsInternal; +use miden_node_validator::{Validator, ValidatorSigner}; + +pub async fn run( + address: SocketAddr, + grpc_options: GrpcOptionsInternal, + signer: ValidatorSigner, + data_directory: PathBuf, +) -> anyhow::Result<()> { + Validator { address, grpc_options, signer, data_directory } + .serve() + .await + .context("failed while serving validator component") +} diff --git a/bin/validator/src/main.rs b/bin/validator/src/main.rs index 2e0552eaff..e0fb6770c2 100644 --- a/bin/validator/src/main.rs +++ b/bin/validator/src/main.rs @@ -1,30 +1,7 @@ -use std::net::SocketAddr; -use std::path::{Path, PathBuf}; - -use anyhow::Context; -use clap::{Parser, Subcommand}; -use miden_node_store::genesis::config::{AccountFileWithName, GenesisConfig}; -use miden_node_utils::clap::GrpcOptionsInternal; -use miden_node_utils::fs::ensure_empty_directory; -use miden_node_utils::grpc::UrlExt; +use clap::Parser; use miden_node_utils::logging::OpenTelemetry; -use miden_node_utils::signer::BlockSigner; -use miden_node_validator::{Validator, ValidatorSigner}; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; -use miden_protocol::utils::serde::{Deserializable, Serializable}; -use url::Url; - -const ENV_DATA_DIRECTORY: &str = "MIDEN_NODE_DATA_DIRECTORY"; -const ENV_ENABLE_OTEL: &str = "MIDEN_NODE_ENABLE_OTEL"; -const ENV_URL: &str = "MIDEN_NODE_VALIDATOR_URL"; -const ENV_GENESIS_CONFIG_FILE: &str = "MIDEN_NODE_VALIDATOR_GENESIS_CONFIG_FILE"; -const ENV_KEY: &str = "MIDEN_NODE_VALIDATOR_KEY"; -const ENV_KMS_KEY_ID: &str = "MIDEN_NODE_VALIDATOR_KMS_KEY_ID"; -/// A predefined, insecure validator key for development purposes. -const INSECURE_KEY_HEX: &str = "0101010101010101010101010101010101010101010101010101010101010101"; -/// The filename used for the genesis block file. -const GENESIS_BLOCK_FILENAME: &str = "genesis.dat"; +mod commands; // CLI // ================================================================================================ @@ -33,7 +10,7 @@ const GENESIS_BLOCK_FILENAME: &str = "genesis.dat"; #[command(version, about, long_about = None)] struct Cli { #[command(subcommand)] - command: ValidatorCommand, + command: commands::ValidatorCommand, } impl Cli { @@ -46,276 +23,6 @@ impl Cli { } } -// COMMANDS -// ================================================================================================ - -#[derive(Subcommand)] -enum ValidatorCommand { - /// Bootstraps the genesis block. - /// - /// Creates accounts from the genesis configuration, builds and signs the genesis block, - /// and writes the signed block and account secret files to disk. Also initializes the - /// validator's database with the genesis block as the chain tip. - Bootstrap { - /// Directory in which to write the genesis block file. - #[arg(long, value_name = "DIR")] - genesis_block_directory: PathBuf, - /// Directory to write the account secret files (.mac) to. - #[arg(long, value_name = "DIR")] - accounts_directory: PathBuf, - /// Directory in which to store the validator's database. - #[arg(long, env = ENV_DATA_DIRECTORY, value_name = "DIR")] - data_directory: PathBuf, - /// Use the given configuration file to construct the genesis state from. - #[arg(long, env = ENV_GENESIS_CONFIG_FILE, value_name = "GENESIS_CONFIG")] - genesis_config_file: Option, - /// Configuration for the Validator key used to sign the genesis block. - #[command(flatten)] - validator_key: ValidatorKey, - }, - - /// Starts the validator component. - Start { - /// Url at which to serve the gRPC API. - #[arg(env = ENV_URL)] - url: Url, - - /// Enables the exporting of traces for OpenTelemetry. - /// - /// This can be further configured using environment variables as defined in the official - /// OpenTelemetry documentation. See our operator manual for further details. - #[arg(long = "enable-otel", default_value_t = false, env = ENV_ENABLE_OTEL, value_name = "BOOL")] - enable_otel: bool, - - #[command(flatten)] - grpc_options: GrpcOptionsInternal, - - /// Directory in which to store the validator's data. - #[arg(long, env = ENV_DATA_DIRECTORY, value_name = "DIR")] - data_directory: PathBuf, - - /// Insecure, hex-encoded validator secret key for development and testing purposes. - /// - /// If not provided, a predefined key is used. - /// - /// Cannot be used with `key.kms-id`. - #[arg( - long = "key.hex", - env = ENV_KEY, - value_name = "VALIDATOR_KEY", - default_value = INSECURE_KEY_HEX, - group = "key" - )] - validator_key: String, - - /// Key ID for the KMS key used by validator to sign blocks. - /// - /// Cannot be used with `key.hex`. - #[arg( - long = "key.kms-id", - env = ENV_KMS_KEY_ID, - value_name = "VALIDATOR_KMS_KEY_ID", - group = "key" - )] - kms_key_id: Option, - }, -} - -impl ValidatorCommand { - async fn handle(self) -> anyhow::Result<()> { - match self { - Self::Bootstrap { - genesis_block_directory, - accounts_directory, - data_directory, - genesis_config_file, - validator_key, - } => { - bootstrap_genesis( - &genesis_block_directory, - &accounts_directory, - &data_directory, - genesis_config_file.as_ref(), - validator_key, - ) - .await - }, - Self::Start { - url, - grpc_options, - validator_key, - data_directory, - kms_key_id, - .. - } => { - let address = url - .to_socket() - .context("failed to extract socket address from validator URL")?; - - if let Some(kms_key_id) = kms_key_id { - let signer = ValidatorSigner::new_kms(kms_key_id).await?; - serve(address, grpc_options, signer, data_directory).await - } else { - let signer = SecretKey::read_from_bytes(hex::decode(validator_key)?.as_ref())?; - let signer = ValidatorSigner::new_local(signer); - serve(address, grpc_options, signer, data_directory).await - } - }, - } - } - - fn is_open_telemetry_enabled(&self) -> bool { - match self { - Self::Start { enable_otel, .. } => *enable_otel, - Self::Bootstrap { .. } => false, - } - } -} - -// VALIDATOR KEY -// ================================================================================================ - -/// Configuration for the Validator key used to sign blocks. -#[derive(clap::Args)] -#[group(required = false, multiple = false)] -struct ValidatorKey { - /// Insecure, hex-encoded validator secret key for development and testing purposes. - /// - /// If not provided, a predefined key is used. - /// - /// Cannot be used with `validator.key.kms-id`. - #[arg( - long = "validator.key.hex", - env = ENV_KEY, - value_name = "VALIDATOR_KEY", - default_value = INSECURE_KEY_HEX, - )] - validator_key: String, - /// Key ID for the KMS key used by validator to sign blocks. - /// - /// Cannot be used with `validator.key.hex`. - #[arg( - long = "validator.key.kms-id", - env = ENV_KMS_KEY_ID, - value_name = "VALIDATOR_KMS_KEY_ID", - )] - validator_kms_key_id: Option, -} - -impl ValidatorKey { - async fn into_signer(self) -> anyhow::Result { - if let Some(kms_key_id) = self.validator_kms_key_id { - Ok(ValidatorSigner::new_kms(kms_key_id).await?) - } else { - let signer = SecretKey::read_from_bytes(hex::decode(self.validator_key)?.as_ref())?; - Ok(ValidatorSigner::new_local(signer)) - } - } -} - -// HELPERS -// ================================================================================================ - -async fn serve( - address: SocketAddr, - grpc_options: GrpcOptionsInternal, - signer: ValidatorSigner, - data_directory: PathBuf, -) -> anyhow::Result<()> { - Validator { address, grpc_options, signer, data_directory } - .serve() - .await - .context("failed while serving validator component") -} - -/// Builds the genesis state, writes account secret files, signs the genesis block, writes it -/// to disk, and initializes the validator's database with the genesis block as the chain tip. -async fn build_and_write_genesis( - config: GenesisConfig, - signer: impl BlockSigner, - accounts_directory: &Path, - genesis_block_directory: &Path, - data_directory: &Path, -) -> anyhow::Result<()> { - let (genesis_state, secrets) = config.into_state(signer)?; - - for item in secrets.as_account_files(&genesis_state) { - let AccountFileWithName { account_file, name } = item?; - let account_path = accounts_directory.join(name); - // Do not override existing keys. - fs_err::OpenOptions::new() - .create_new(true) - .write(true) - .open(&account_path) - .context("key file already exists")?; - account_file.write(account_path)?; - } - - let genesis_block = - genesis_state.into_block().await.context("failed to build the genesis block")?; - - let block_bytes = genesis_block.inner().to_bytes(); - let genesis_block_path = genesis_block_directory.join(GENESIS_BLOCK_FILENAME); - fs_err::write(&genesis_block_path, block_bytes).context("failed to write genesis block")?; - - let (genesis_header, ..) = genesis_block.into_inner().into_parts(); - let db = miden_node_validator::db::load(data_directory.join("validator.sqlite3")) - .await - .context("failed to initialize validator database during bootstrap")?; - db.transact("upsert_block_header", move |conn| { - miden_node_validator::db::upsert_block_header(conn, &genesis_header) - }) - .await - .context("failed to persist genesis block header as chain tip")?; - - Ok(()) -} - -async fn bootstrap_genesis( - genesis_block_directory: &Path, - accounts_directory: &Path, - data_directory: &Path, - genesis_config: Option<&PathBuf>, - validator_key: ValidatorKey, -) -> anyhow::Result<()> { - let config = genesis_config - .map(|file_path| { - GenesisConfig::read_toml_file(file_path).with_context(|| { - format!("failed to parse genesis config from file {}", file_path.display()) - }) - }) - .transpose()? - .unwrap_or_default(); - - for directory in [accounts_directory, genesis_block_directory] { - ensure_empty_directory(directory)?; - } - - let signer = validator_key.into_signer().await?; - match signer { - ValidatorSigner::Kms(signer) => { - build_and_write_genesis( - config, - signer, - accounts_directory, - genesis_block_directory, - data_directory, - ) - .await - }, - ValidatorSigner::Local(signer) => { - build_and_write_genesis( - config, - signer, - accounts_directory, - genesis_block_directory, - data_directory, - ) - .await - }, - } -} - // MAIN // ================================================================================================ From 3d556107c1962dccee80a6ae1161e267663a09f4 Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 6 May 2026 12:41:40 +1200 Subject: [PATCH 03/16] Add validator Debian package --- .github/workflows/publish-debian-all.yml | 28 +++++++++++++ .github/workflows/publish-debian.yml | 6 ++- bin/node/.env | 6 --- bin/validator/.env | 9 +++++ packaging/node/postinst | 39 +++++++++---------- packaging/node/postrm | 12 +++--- .../miden-validator.service | 2 +- packaging/validator/postinst | 28 +++++++++++++ packaging/validator/postrm | 12 ++++++ 9 files changed, 107 insertions(+), 35 deletions(-) create mode 100644 bin/validator/.env rename packaging/{node => validator}/miden-validator.service (87%) create mode 100644 packaging/validator/postinst create mode 100644 packaging/validator/postrm diff --git a/.github/workflows/publish-debian-all.yml b/.github/workflows/publish-debian-all.yml index 201412e39b..9ed73569f1 100644 --- a/.github/workflows/publish-debian-all.yml +++ b/.github/workflows/publish-debian-all.yml @@ -72,6 +72,34 @@ jobs: crate: miden-remote-prover arch: ${{ matrix.arch }} + publish-validator: + name: Publish Validator ${{ matrix.arch }} Debian + permissions: + contents: write + strategy: + matrix: + arch: [amd64, arm64] + runs-on: + labels: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} + steps: + - name: Checkout repo + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + fetch-depth: 0 + persist-credentials: false + - uses: ./.github/actions/install-rocksdb + - uses: ./.github/actions/install-protobuf-compiler + - name: Build and Publish Validator + uses: ./.github/actions/debian + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + gitref: ${{ env.version }} + crate_dir: validator + package: miden-validator + packaging_dir: validator + crate: miden-validator + arch: ${{ matrix.arch }} + publish-network-monitor: name: Publish Network Monitor ${{ matrix.arch }} Debian permissions: diff --git a/.github/workflows/publish-debian.yml b/.github/workflows/publish-debian.yml index a9550ff33b..9419b94775 100644 --- a/.github/workflows/publish-debian.yml +++ b/.github/workflows/publish-debian.yml @@ -11,7 +11,7 @@ on: - miden-network-monitor - miden-node - miden-prover - - miden-prover-proxy + - miden-validator crate_dir: required: true description: "Name of crate directory" @@ -20,6 +20,7 @@ on: - network-monitor - node - remote-prover + - validator packaging_dir: required: true description: "Name of packaging directory" @@ -28,7 +29,7 @@ on: - network-monitor - node - prover - - prover-proxy + - validator crate: description: "Name of the binary crate to publish" required: true @@ -37,6 +38,7 @@ on: - miden-network-monitor - miden-node - miden-remote-prover + - miden-validator version: description: "Version to release (E.G. v0.10.0-rc.1, v0.10.0). Corresponding git tag must already exist." required: true diff --git a/bin/node/.env b/bin/node/.env index dbb6355216..16b3c1bbdd 100644 --- a/bin/node/.env +++ b/bin/node/.env @@ -28,12 +28,6 @@ MIDEN_NODE_RPC_BLOCK_PRODUCER_URL= MIDEN_NODE_RPC_VALIDATOR_URL= MIDEN_NODE_RPC_NTX_BUILDER_URL= -# Validator -MIDEN_NODE_VALIDATOR_URL= -MIDEN_NODE_VALIDATOR_GENESIS_CONFIG_FILE= -MIDEN_NODE_VALIDATOR_KEY= -MIDEN_NODE_VALIDATOR_KMS_KEY_ID= - # NTX Builder MIDEN_NODE_NTX_BUILDER_URL= MIDEN_NODE_NTX_BUILDER_STORE_URL= diff --git a/bin/validator/.env b/bin/validator/.env new file mode 100644 index 0000000000..c6eddf29bb --- /dev/null +++ b/bin/validator/.env @@ -0,0 +1,9 @@ +# For more info use -h on the relevant commands: +# miden-validator start -h + +MIDEN_NODE_ENABLE_OTEL=true +MIDEN_NODE_DATA_DIRECTORY= +MIDEN_NODE_VALIDATOR_URL= +MIDEN_NODE_VALIDATOR_GENESIS_CONFIG_FILE= +MIDEN_NODE_VALIDATOR_KEY= +MIDEN_NODE_VALIDATOR_KMS_KEY_ID= diff --git a/packaging/node/postinst b/packaging/node/postinst index 036b2d112a..a6365a7ac6 100644 --- a/packaging/node/postinst +++ b/packaging/node/postinst @@ -2,28 +2,27 @@ # # This is a postinstallation script so the service can be configured and started when requested. -for svc in miden-node miden-validator; do - # user is expected by the systemd service file and `/opt/` is its working directory, - sudo adduser --disabled-password --disabled-login --shell /usr/sbin/nologin --quiet --system --no-create-home --home /nonexistent "$svc" +svc=miden-node - # Working folder. - if [ -d "/opt/$svc" ] - then - echo "Directory /opt/$svc exists." - else - mkdir -p "/opt/$svc" - fi - sudo chown -R "$svc" "/opt/$svc" +# user is expected by the systemd service file and `/opt/` is its working directory, +sudo adduser --disabled-password --disabled-login --shell /usr/sbin/nologin --quiet --system --no-create-home --home /nonexistent "$svc" - # Configuration folder - if [ -d "/etc/opt/$svc" ] - then - echo "Directory /etc/opt/$svc exists." - else - mkdir -p "/etc/opt/$svc" - fi - sudo chown -R "$svc" "/etc/opt/$svc" +# Working folder. +if [ -d "/opt/$svc" ] +then + echo "Directory /opt/$svc exists." +else + mkdir -p "/opt/$svc" +fi +sudo chown -R "$svc" "/opt/$svc" -done +# Configuration folder +if [ -d "/etc/opt/$svc" ] +then + echo "Directory /etc/opt/$svc exists." +else + mkdir -p "/etc/opt/$svc" +fi +sudo chown -R "$svc" "/etc/opt/$svc" sudo systemctl daemon-reload diff --git a/packaging/node/postrm b/packaging/node/postrm index 86a9846a25..e87c22c55f 100644 --- a/packaging/node/postrm +++ b/packaging/node/postrm @@ -1,12 +1,12 @@ #!/bin/bash # ############### -# Remove miden-node installs +# Remove miden-node install ############## -for svc in miden-node miden-validator; do - sudo rm -rf "/lib/systemd/system/$svc.service" - sudo rm -rf "/etc/opt/$svc" - sudo deluser "$svc" -done +svc=miden-node + +sudo rm -rf "/lib/systemd/system/$svc.service" +sudo rm -rf "/etc/opt/$svc" +sudo deluser "$svc" sudo systemctl daemon-reload diff --git a/packaging/node/miden-validator.service b/packaging/validator/miden-validator.service similarity index 87% rename from packaging/node/miden-validator.service rename to packaging/validator/miden-validator.service index 7b6c5de874..68ebe5cb01 100644 --- a/packaging/node/miden-validator.service +++ b/packaging/validator/miden-validator.service @@ -9,7 +9,7 @@ WantedBy=multi-user.target Type=exec Environment="OTEL_SERVICE_NAME=miden-validator" EnvironmentFile=/lib/systemd/system/miden-validator.env -ExecStart=/usr/bin/miden-node validator start +ExecStart=/usr/bin/miden-validator start WorkingDirectory=/opt/miden-validator User=miden-validator RestartSec=5 diff --git a/packaging/validator/postinst b/packaging/validator/postinst new file mode 100644 index 0000000000..53d185ca3d --- /dev/null +++ b/packaging/validator/postinst @@ -0,0 +1,28 @@ +#!/bin/bash +# +# This is a postinstallation script so the service can be configured and started when requested. + +svc=miden-validator + +# user is expected by the systemd service file and `/opt/` is its working directory, +sudo adduser --disabled-password --disabled-login --shell /usr/sbin/nologin --quiet --system --no-create-home --home /nonexistent "$svc" + +# Working folder. +if [ -d "/opt/$svc" ] +then + echo "Directory /opt/$svc exists." +else + mkdir -p "/opt/$svc" +fi +sudo chown -R "$svc" "/opt/$svc" + +# Configuration folder +if [ -d "/etc/opt/$svc" ] +then + echo "Directory /etc/opt/$svc exists." +else + mkdir -p "/etc/opt/$svc" +fi +sudo chown -R "$svc" "/etc/opt/$svc" + +sudo systemctl daemon-reload diff --git a/packaging/validator/postrm b/packaging/validator/postrm new file mode 100644 index 0000000000..d801ef8dd6 --- /dev/null +++ b/packaging/validator/postrm @@ -0,0 +1,12 @@ +#!/bin/bash +# +############### +# Remove miden-validator install +############## +svc=miden-validator + +sudo rm -rf "/lib/systemd/system/$svc.service" +sudo rm -rf "/etc/opt/$svc" +sudo deluser "$svc" + +sudo systemctl daemon-reload From fc6be762170f8b23e357b70573c4156b504dc25e Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 6 May 2026 12:48:58 +1200 Subject: [PATCH 04/16] Docs, Makefile, and compose --- Makefile | 20 +++++++++++++++++++- docker-compose.yml | 26 ++++++++++++++++++++------ docs/external/src/operator/usage.md | 8 ++++---- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index ca2481d1ee..4bc0b23fe8 100644 --- a/Makefile +++ b/Makefile @@ -105,6 +105,10 @@ build: ## Builds all crates and re-builds protobuf bindings for proto crates install-node: ## Installs node cargo install --path bin/node --locked +.PHONY: install-validator +install-validator: ## Installs validator + cargo install --path bin/validator --locked + .PHONY: install-remote-prover install-remote-prover: ## Install remote prover's CLI cargo install --path bin/remote-prover --bin miden-remote-prover --locked @@ -132,7 +136,7 @@ install-network-monitor: ## Installs network monitor binary compose-genesis: ## Wipes node volumes and creates a fresh genesis block $(CONTAINER_RUNTIME) compose $(COMPOSE_FILES) down --volumes --remove-orphans $(CONTAINER_RUNTIME) volume rm -f miden-node_node-data - $(CONTAINER_RUNTIME) compose $(COMPOSE_FILES) --profile genesis run --rm genesis + $(CONTAINER_RUNTIME) compose $(COMPOSE_FILES) --profile genesis run --rm genesis-store .PHONY: compose-up compose-up: ## Starts all node components, telemetry, and monitor via docker compose @@ -146,6 +150,9 @@ compose-down: ## Stops and removes all containers via docker compose compose-logs: ## Follows logs for all components via docker compose $(CONTAINER_RUNTIME) compose $(COMPOSE_FILES) logs -f +.PHONY: docker-build +docker-build: docker-build-node docker-build-validator ## Builds all Docker images + .PHONY: docker-build-node docker-build-node: ## Builds the Miden node using Docker (override with CONTAINER_RUNTIME=podman) @CREATED=$$(date) && \ @@ -157,6 +164,17 @@ docker-build-node: ## Builds the Miden node using Docker (override with CONTAINE -f bin/node/Dockerfile \ -t miden-node-image . +.PHONY: docker-build-validator +docker-build-validator: ## Builds the Miden validator using Docker (override with CONTAINER_RUNTIME=podman) + @CREATED=$$(date) && \ + VERSION=$$(cat bin/validator/Cargo.toml | grep -m 1 '^version' | cut -d '"' -f 2) && \ + COMMIT=$$(git rev-parse HEAD) && \ + $(CONTAINER_RUNTIME) build --build-arg CREATED="$$CREATED" \ + --build-arg VERSION="$$VERSION" \ + --build-arg COMMIT="$$COMMIT" \ + -f bin/validator/Dockerfile \ + -t miden-validator-image . + .PHONY: docker-build-monitor docker-build-monitor: ## Builds the network monitor using Docker (override with CONTAINER_RUNTIME=podman) $(CONTAINER_RUNTIME) build \ diff --git a/docker-compose.yml b/docker-compose.yml index cd5594507b..ff41924a9e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: - genesis: - image: miden-node-image + genesis-validator: + image: miden-validator-image pull_policy: if_not_present profiles: - genesis @@ -12,10 +12,25 @@ services: set -e mkdir -p /data/genesis /data/store /data/validator /data/accounts /data/ntx-builder echo "Bootstrapping validator (creating genesis block)..." - miden-node validator bootstrap \ + miden-validator bootstrap \ --data-directory /data/validator \ --genesis-block-directory /data/genesis \ --accounts-directory /data/accounts + + genesis-store: + image: miden-node-image + pull_policy: if_not_present + profiles: + - genesis + volumes: + - node-data:/data + entrypoint: ["/bin/sh", "-c"] + depends_on: + genesis-validator: + condition: service_completed_successfully + command: + - | + set -e echo "Bootstrapping store..." miden-node store bootstrap \ --data-directory /data/store \ @@ -48,7 +63,7 @@ services: - "50003:50003" validator: - image: miden-node-image + image: miden-validator-image pull_policy: if_not_present volumes: - node-data:/data @@ -57,8 +72,7 @@ services: - OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4317 - OTEL_SERVICE_NAME=validator command: - - miden-node - - validator + - miden-validator - start - http://0.0.0.0:50101 - --data-directory=/data/validator diff --git a/docs/external/src/operator/usage.md b/docs/external/src/operator/usage.md index 62d05b31a8..041461b5f2 100644 --- a/docs/external/src/operator/usage.md +++ b/docs/external/src/operator/usage.md @@ -20,7 +20,7 @@ its database from that block. By default the genesis block will contain a single ```sh # Step 1: Validator bootstrap — create the signed genesis block and account files. -miden-node validator bootstrap \ +miden-validator bootstrap \ --genesis-block-directory genesis-data \ --accounts-directory accounts @@ -36,7 +36,7 @@ transactions to achieve the desired state. Any account secrets will be written t the provided `--accounts-directory` path in the process. ```sh -miden-node validator bootstrap \ +miden-validator bootstrap \ --genesis-block-directory genesis-data \ --accounts-directory accounts \ --genesis-config-file genesis.toml @@ -110,7 +110,7 @@ The default `compose-up` target starts all node components along with a telemetr a network monitor: ```sh -make docker-build-node +make docker-build make docker-build-monitor make compose-genesis make compose-up @@ -166,7 +166,7 @@ miden-node store start \ --data-directory /tmp/store # Start the validator -miden-node validator start http://0.0.0.0:50101 \ +miden-validator start http://0.0.0.0:50101 \ --data-directory /tmp/validator # Start the block producer From cbba850149484a5296c907acd4d09bf015a98896 Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 6 May 2026 13:05:51 +1200 Subject: [PATCH 05/16] Coalesce redundant workflow options --- .github/workflows/publish-debian.yml | 64 +++++++++++++--------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/.github/workflows/publish-debian.yml b/.github/workflows/publish-debian.yml index 9419b94775..478ea46d96 100644 --- a/.github/workflows/publish-debian.yml +++ b/.github/workflows/publish-debian.yml @@ -3,42 +3,15 @@ name: Publish Debian Package on: workflow_dispatch: inputs: - package: - description: "Name of package to publish" + component: + description: "Component to publish" required: true type: choice - options: - - miden-network-monitor - - miden-node - - miden-prover - - miden-validator - crate_dir: - required: true - description: "Name of crate directory" - type: choice - options: - - network-monitor - - node - - remote-prover - - validator - packaging_dir: - required: true - description: "Name of packaging directory" - type: choice options: - network-monitor - node - prover - validator - crate: - description: "Name of the binary crate to publish" - required: true - type: choice - options: - - miden-network-monitor - - miden-node - - miden-remote-prover - - miden-validator version: description: "Version to release (E.G. v0.10.0-rc.1, v0.10.0). Corresponding git tag must already exist." required: true @@ -49,7 +22,7 @@ permissions: jobs: publish: - name: Publish ${{ inputs.package }} ${{ matrix.arch }} Debian + name: Publish ${{ inputs.component }} ${{ matrix.arch }} Debian permissions: contents: write strategy: @@ -64,6 +37,29 @@ jobs: fetch-depth: 0 persist-credentials: false + - name: Resolve component inputs + id: resolve + shell: bash + env: + COMPONENT: ${{ inputs.component }} + run: | + package="miden-${COMPONENT}" + packaging_dir="${COMPONENT}" + + # prover's binary crate lives in bin/remote-prover and is named miden-remote-prover + if [ "${COMPONENT}" = "prover" ]; then + crate_dir="remote-prover" + crate="miden-remote-prover" + else + crate_dir="${COMPONENT}" + crate="miden-${COMPONENT}" + fi + + echo "package=${package}" >> "$GITHUB_OUTPUT" + echo "crate_dir=${crate_dir}" >> "$GITHUB_OUTPUT" + echo "packaging_dir=${packaging_dir}" >> "$GITHUB_OUTPUT" + echo "crate=${crate}" >> "$GITHUB_OUTPUT" + - uses: ./.github/actions/install-rocksdb - uses: ./.github/actions/install-protobuf-compiler @@ -72,8 +68,8 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} gitref: ${{ inputs.version }} - crate_dir: ${{ inputs.crate_dir }} - package: ${{ inputs.package }} - packaging_dir: ${{ inputs.packaging_dir }} - crate: ${{ inputs.crate }} + crate_dir: ${{ steps.resolve.outputs.crate_dir }} + package: ${{ steps.resolve.outputs.package }} + packaging_dir: ${{ steps.resolve.outputs.packaging_dir }} + crate: ${{ steps.resolve.outputs.crate }} arch: ${{ matrix.arch }} From b28ce85408f5386b478e692e89303f111e57139f Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 6 May 2026 13:58:10 +1200 Subject: [PATCH 06/16] Lint --- CHANGELOG.md | 2 ++ Cargo.lock | 1 - bin/node/Cargo.toml | 1 - bin/validator/src/commands/bootstrap.rs | 18 ++++++++++++++++-- bin/validator/src/commands/start.rs | 13 +++++++++---- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f75985f53..6165987710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ - Added a `replica` mode to the store, which streams blocks from an upstream master store ([#1987](https://github.com/0xMiden/node/pull/1987)). - Added `StoreReplica` gRPC service with endpoints for streaming blocks and proofs ([#1987](https://github.com/0xMiden/node/pull/1987)). - Replaced the network monitor's JavaScript dashboard with a server-rendered Maud + HTMX frontend ([#2024](https://github.com/0xMiden/node/pull/2024)). +- Removed `miden-node validator` subcommand and created a separate `miden-validator` binary ([#2053](https://github.com/0xMiden/node/pull/2053)). + ## v0.14.10 (2026-05-29) diff --git a/Cargo.lock b/Cargo.lock index 0e7b18daa1..3902a81bfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3206,7 +3206,6 @@ dependencies = [ "anyhow", "clap", "fs-err", - "hex", "humantime", "miden-node-block-producer", "miden-node-ntx-builder", diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index 4968bd9a2c..cba7708418 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -21,7 +21,6 @@ tracing-forest = ["miden-node-block-producer/tracing-forest"] anyhow = { workspace = true } clap = { features = ["env", "string"], workspace = true } fs-err = { workspace = true } -hex = { workspace = true } humantime = { workspace = true } miden-node-block-producer = { workspace = true } miden-node-ntx-builder = { workspace = true } diff --git a/bin/validator/src/commands/bootstrap.rs b/bin/validator/src/commands/bootstrap.rs index 2c89ee2342..02791102b4 100644 --- a/bin/validator/src/commands/bootstrap.rs +++ b/bin/validator/src/commands/bootstrap.rs @@ -34,10 +34,24 @@ pub async fn run( let signer = validator_key.into_signer().await?; match signer { ValidatorSigner::Kms(signer) => { - build_and_write_genesis(config, signer, accounts_directory, genesis_block_directory, data_directory).await + build_and_write_genesis( + config, + signer, + accounts_directory, + genesis_block_directory, + data_directory, + ) + .await }, ValidatorSigner::Local(signer) => { - build_and_write_genesis(config, signer, accounts_directory, genesis_block_directory, data_directory).await + build_and_write_genesis( + config, + signer, + accounts_directory, + genesis_block_directory, + data_directory, + ) + .await }, } } diff --git a/bin/validator/src/commands/start.rs b/bin/validator/src/commands/start.rs index 1bea5470ef..8c47e97cfb 100644 --- a/bin/validator/src/commands/start.rs +++ b/bin/validator/src/commands/start.rs @@ -11,8 +11,13 @@ pub async fn run( signer: ValidatorSigner, data_directory: PathBuf, ) -> anyhow::Result<()> { - Validator { address, grpc_options, signer, data_directory } - .serve() - .await - .context("failed while serving validator component") + Validator { + address, + grpc_options, + signer, + data_directory, + } + .serve() + .await + .context("failed while serving validator component") } From 15d93a927012b906d0a1bf40ba01041f7097aa0a Mon Sep 17 00:00:00 2001 From: sergerad Date: Wed, 6 May 2026 14:15:54 +1200 Subject: [PATCH 07/16] Rename command fns --- bin/validator/src/commands/bootstrap.rs | 3 ++- bin/validator/src/commands/mod.rs | 6 +++--- bin/validator/src/commands/start.rs | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/bin/validator/src/commands/bootstrap.rs b/bin/validator/src/commands/bootstrap.rs index 02791102b4..f4f95c0b37 100644 --- a/bin/validator/src/commands/bootstrap.rs +++ b/bin/validator/src/commands/bootstrap.rs @@ -11,7 +11,8 @@ use super::ValidatorKey; const GENESIS_BLOCK_FILENAME: &str = "genesis.dat"; -pub async fn run( +// Bootstraps the validator component. +pub async fn bootstrap( genesis_block_directory: &Path, accounts_directory: &Path, data_directory: &Path, diff --git a/bin/validator/src/commands/mod.rs b/bin/validator/src/commands/mod.rs index da56a7757b..9362e6eaec 100644 --- a/bin/validator/src/commands/mod.rs +++ b/bin/validator/src/commands/mod.rs @@ -108,7 +108,7 @@ impl ValidatorCommand { genesis_config_file, validator_key, } => { - bootstrap::run( + bootstrap::bootstrap( &genesis_block_directory, &accounts_directory, &data_directory, @@ -131,11 +131,11 @@ impl ValidatorCommand { if let Some(kms_key_id) = kms_key_id { let signer = ValidatorSigner::new_kms(kms_key_id).await?; - start::run(address, grpc_options, signer, data_directory).await + start::start(address, grpc_options, signer, data_directory).await } else { let signer = SecretKey::read_from_bytes(hex::decode(validator_key)?.as_ref())?; let signer = ValidatorSigner::new_local(signer); - start::run(address, grpc_options, signer, data_directory).await + start::start(address, grpc_options, signer, data_directory).await } }, } diff --git a/bin/validator/src/commands/start.rs b/bin/validator/src/commands/start.rs index 8c47e97cfb..c8d1273c3d 100644 --- a/bin/validator/src/commands/start.rs +++ b/bin/validator/src/commands/start.rs @@ -5,7 +5,8 @@ use anyhow::Context; use miden_node_utils::clap::GrpcOptionsInternal; use miden_node_validator::{Validator, ValidatorSigner}; -pub async fn run( +// Starts the validator component. +pub async fn start( address: SocketAddr, grpc_options: GrpcOptionsInternal, signer: ValidatorSigner, From 1b4da984d26a399c393c713886c8f3a85f9eb1e6 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 7 May 2026 11:04:33 +1200 Subject: [PATCH 08/16] RM -image suffix --- Makefile | 6 +++--- docker-compose.yml | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 4bc0b23fe8..31605a4b0b 100644 --- a/Makefile +++ b/Makefile @@ -162,7 +162,7 @@ docker-build-node: ## Builds the Miden node using Docker (override with CONTAINE --build-arg VERSION="$$VERSION" \ --build-arg COMMIT="$$COMMIT" \ -f bin/node/Dockerfile \ - -t miden-node-image . + -t miden-node . .PHONY: docker-build-validator docker-build-validator: ## Builds the Miden validator using Docker (override with CONTAINER_RUNTIME=podman) @@ -173,7 +173,7 @@ docker-build-validator: ## Builds the Miden validator using Docker (override wit --build-arg VERSION="$$VERSION" \ --build-arg COMMIT="$$COMMIT" \ -f bin/validator/Dockerfile \ - -t miden-validator-image . + -t miden-validator . .PHONY: docker-build-monitor docker-build-monitor: ## Builds the network monitor using Docker (override with CONTAINER_RUNTIME=podman) @@ -187,7 +187,7 @@ docker-run-node: ## Runs the Miden node as a Docker container (override with CON $(CONTAINER_RUNTIME) run --name miden-node \ -p 57291:57291 \ -v miden-db:/db \ - -d miden-node-image + -d miden-node ## --- setup -------------------------------------------------------------------------------------- diff --git a/docker-compose.yml b/docker-compose.yml index ff41924a9e..45b00d310d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: genesis-validator: - image: miden-validator-image + image: miden-validator pull_policy: if_not_present profiles: - genesis @@ -18,7 +18,7 @@ services: --accounts-directory /data/accounts genesis-store: - image: miden-node-image + image: miden-node pull_policy: if_not_present profiles: - genesis @@ -37,7 +37,7 @@ services: --genesis-block /data/genesis/genesis.dat store: - image: miden-node-image + image: miden-node pull_policy: if_not_present volumes: - node-data:/data @@ -63,7 +63,7 @@ services: - "50003:50003" validator: - image: miden-validator-image + image: miden-validator pull_policy: if_not_present volumes: - node-data:/data @@ -80,7 +80,7 @@ services: - "50101:50101" block-producer: - image: miden-node-image + image: miden-node pull_policy: if_not_present environment: - MIDEN_NODE_ENABLE_OTEL=true @@ -97,7 +97,7 @@ services: - "50201:50201" rpc: - image: miden-node-image + image: miden-node pull_policy: if_not_present environment: - MIDEN_NODE_ENABLE_OTEL=true @@ -115,7 +115,7 @@ services: - "57291:57291" ntx-builder: - image: miden-node-image + image: miden-node pull_policy: if_not_present volumes: - node-data:/data From 948785c63be6959b6a7c576911c3bb88e3c79a5f Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 7 May 2026 11:05:03 +1200 Subject: [PATCH 09/16] RM newline in changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6165987710..2e325eca2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,6 @@ - Replaced the network monitor's JavaScript dashboard with a server-rendered Maud + HTMX frontend ([#2024](https://github.com/0xMiden/node/pull/2024)). - Removed `miden-node validator` subcommand and created a separate `miden-validator` binary ([#2053](https://github.com/0xMiden/node/pull/2053)). - ## v0.14.10 (2026-05-29) - Optimize `GetAccount` implementation to serve vault assets from `AccountStateForest` ([#1981](https://github.com/0xMiden/node/pull/1981)). From 5a2510d12d92ab93ff1c14dcf6e89cb5e5103112 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 7 May 2026 11:08:45 +1200 Subject: [PATCH 10/16] RM CLI boilerplate --- bin/validator/src/commands/mod.rs | 5 +++-- bin/validator/src/main.rs | 32 +++++++++---------------------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/bin/validator/src/commands/mod.rs b/bin/validator/src/commands/mod.rs index 9362e6eaec..67c01b41a5 100644 --- a/bin/validator/src/commands/mod.rs +++ b/bin/validator/src/commands/mod.rs @@ -4,7 +4,7 @@ mod start; use std::path::PathBuf; use anyhow::Context; -use clap::Subcommand; +use clap::Parser; use miden_node_utils::clap::GrpcOptionsInternal; use miden_node_utils::grpc::UrlExt; use miden_node_validator::ValidatorSigner; @@ -26,7 +26,8 @@ pub(crate) const INSECURE_KEY_HEX: &str = // VALIDATOR COMMAND // ================================================================================================ -#[derive(Subcommand)] +#[derive(Parser)] +#[command(version, about, long_about = None)] pub enum ValidatorCommand { /// Bootstraps the genesis block. /// diff --git a/bin/validator/src/main.rs b/bin/validator/src/main.rs index e0fb6770c2..6f27a578cc 100644 --- a/bin/validator/src/main.rs +++ b/bin/validator/src/main.rs @@ -3,34 +3,20 @@ use miden_node_utils::logging::OpenTelemetry; mod commands; -// CLI -// ================================================================================================ - -#[derive(Parser)] -#[command(version, about, long_about = None)] -struct Cli { - #[command(subcommand)] - command: commands::ValidatorCommand, -} - -impl Cli { - fn open_telemetry(&self) -> OpenTelemetry { - if self.command.is_open_telemetry_enabled() { - OpenTelemetry::Enabled - } else { - OpenTelemetry::Disabled - } - } -} - // MAIN // ================================================================================================ #[tokio::main] async fn main() -> anyhow::Result<()> { - let cli = Cli::parse(); + let command = commands::ValidatorCommand::parse(); + + let otel = if command.is_open_telemetry_enabled() { + OpenTelemetry::Enabled + } else { + OpenTelemetry::Disabled + }; - let _otel_guard = miden_node_utils::logging::setup_tracing(cli.open_telemetry())?; + let _otel_guard = miden_node_utils::logging::setup_tracing(otel)?; - cli.command.handle().await + command.handle().await } From f841cb813fabd671f49900ee2aa4d78bfdec8055 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 7 May 2026 12:19:36 +1200 Subject: [PATCH 11/16] Merge dockerfiles and fix makefile --- bin/node/Dockerfile => Dockerfile | 19 +++++++---- Makefile | 46 +++++++++++++------------- bin/validator/Dockerfile | 54 ------------------------------- 3 files changed, 36 insertions(+), 83 deletions(-) rename bin/node/Dockerfile => Dockerfile (83%) delete mode 100644 bin/validator/Dockerfile diff --git a/bin/node/Dockerfile b/Dockerfile similarity index 83% rename from bin/node/Dockerfile rename to Dockerfile index 89560f0891..21605c1325 100644 --- a/bin/node/Dockerfile +++ b/Dockerfile @@ -1,3 +1,6 @@ +ARG BIN +ARG PORT + FROM rust:1.93-slim-bookworm AS chef # Install build dependencies. RocksDB is compiled from source by librocksdb-sys. RUN apt-get update && \ @@ -20,12 +23,13 @@ COPY . . RUN cargo chef prepare --recipe-path recipe.json FROM chef AS builder +ARG BIN COPY --from=planner /app/recipe.json recipe.json # Build dependencies - this is the caching Docker layer! RUN cargo chef cook --release --recipe-path recipe.json # Build application COPY . . -RUN cargo build --release --locked --bin miden-node +RUN cargo build --release --locked --bin ${BIN} # Base line runtime image with runtime dependencies installed. FROM debian:bookworm-slim AS runtime-base @@ -35,7 +39,9 @@ RUN apt-get update && \ && rm -rf /var/lib/apt/lists/* FROM runtime-base AS runtime -COPY --from=builder /app/target/release/miden-node /usr/local/bin/miden-node +ARG BIN +ARG PORT +COPY --from=builder /app/target/release/${BIN} /usr/local/bin/${BIN} LABEL org.opencontainers.image.authors=devops@miden.team \ org.opencontainers.image.url=https://0xMiden.github.io/ \ org.opencontainers.image.documentation=https://github.com/0xMiden/node \ @@ -48,8 +54,7 @@ ARG COMMIT LABEL org.opencontainers.image.created=$CREATED \ org.opencontainers.image.version=$VERSION \ org.opencontainers.image.revision=$COMMIT - -# Expose RPC port -EXPOSE 57291 -# Miden node does not spawn sub-processes, so it can be used as the PID1. -CMD ["miden-node"] +EXPOSE ${PORT} +# Use exec to replace the shell so the binary runs as PID 1. +ENV MIDEN_BIN=${BIN} +CMD ["/bin/sh", "-c", "exec /usr/local/bin/$MIDEN_BIN"] diff --git a/Makefile b/Makefile index 31605a4b0b..aa6a035b47 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,6 @@ help: # -- variables ------------------------------------------------------------------------------------ WARNINGS=RUSTDOCFLAGS="-D warnings" -CONTAINER_RUNTIME ?= docker STRESS_TEST_DATA_DIR ?= stress-test-store-$(shell date +%Y%m%d-%H%M%S) COMPOSE_FILES = -f docker-compose.yml -f compose/telemetry.yml -f compose/monitor.yml @@ -134,57 +133,60 @@ install-network-monitor: ## Installs network monitor binary .PHONY: compose-genesis compose-genesis: ## Wipes node volumes and creates a fresh genesis block - $(CONTAINER_RUNTIME) compose $(COMPOSE_FILES) down --volumes --remove-orphans - $(CONTAINER_RUNTIME) volume rm -f miden-node_node-data - $(CONTAINER_RUNTIME) compose $(COMPOSE_FILES) --profile genesis run --rm genesis-store + docker compose $(COMPOSE_FILES) down --volumes --remove-orphans + docker volume rm -f miden-node_node-data + docker compose $(COMPOSE_FILES) --profile genesis run --rm genesis-store + docker compose $(COMPOSE_FILES) --profile genesis rm -f .PHONY: compose-up compose-up: ## Starts all node components, telemetry, and monitor via docker compose - $(CONTAINER_RUNTIME) compose $(COMPOSE_FILES) up -d + docker compose $(COMPOSE_FILES) up -d .PHONY: compose-down compose-down: ## Stops and removes all containers via docker compose - $(CONTAINER_RUNTIME) compose $(COMPOSE_FILES) down + docker compose $(COMPOSE_FILES) down .PHONY: compose-logs compose-logs: ## Follows logs for all components via docker compose - $(CONTAINER_RUNTIME) compose $(COMPOSE_FILES) logs -f + docker compose $(COMPOSE_FILES) logs -f .PHONY: docker-build docker-build: docker-build-node docker-build-validator ## Builds all Docker images .PHONY: docker-build-node -docker-build-node: ## Builds the Miden node using Docker (override with CONTAINER_RUNTIME=podman) +docker-build-node: ## Builds the Miden node using Docker @CREATED=$$(date) && \ VERSION=$$(cat bin/node/Cargo.toml | grep -m 1 '^version' | cut -d '"' -f 2) && \ COMMIT=$$(git rev-parse HEAD) && \ - $(CONTAINER_RUNTIME) build --build-arg CREATED="$$CREATED" \ - --build-arg VERSION="$$VERSION" \ - --build-arg COMMIT="$$COMMIT" \ - -f bin/node/Dockerfile \ + docker build --build-arg CREATED="$$CREATED" \ + --build-arg VERSION="$$VERSION" \ + --build-arg COMMIT="$$COMMIT" \ + --build-arg BIN=miden-node \ + --build-arg PORT=57291 \ -t miden-node . .PHONY: docker-build-validator -docker-build-validator: ## Builds the Miden validator using Docker (override with CONTAINER_RUNTIME=podman) +docker-build-validator: ## Builds the Miden validator using Docker @CREATED=$$(date) && \ VERSION=$$(cat bin/validator/Cargo.toml | grep -m 1 '^version' | cut -d '"' -f 2) && \ COMMIT=$$(git rev-parse HEAD) && \ - $(CONTAINER_RUNTIME) build --build-arg CREATED="$$CREATED" \ - --build-arg VERSION="$$VERSION" \ - --build-arg COMMIT="$$COMMIT" \ - -f bin/validator/Dockerfile \ + docker build --build-arg CREATED="$$CREATED" \ + --build-arg VERSION="$$VERSION" \ + --build-arg COMMIT="$$COMMIT" \ + --build-arg BIN=miden-validator \ + --build-arg PORT=50101 \ -t miden-validator . .PHONY: docker-build-monitor -docker-build-monitor: ## Builds the network monitor using Docker (override with CONTAINER_RUNTIME=podman) - $(CONTAINER_RUNTIME) build \ +docker-build-monitor: ## Builds the network monitor using Docker + docker build \ -f bin/network-monitor/Dockerfile \ -t miden-network-monitor-image . .PHONY: docker-run-node -docker-run-node: ## Runs the Miden node as a Docker container (override with CONTAINER_RUNTIME=podman) - $(CONTAINER_RUNTIME) volume create miden-db - $(CONTAINER_RUNTIME) run --name miden-node \ +docker-run-node: ## Runs the Miden node as a Docker container + docker volume create miden-db + docker run --name miden-node \ -p 57291:57291 \ -v miden-db:/db \ -d miden-node diff --git a/bin/validator/Dockerfile b/bin/validator/Dockerfile deleted file mode 100644 index 887463f1fd..0000000000 --- a/bin/validator/Dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -FROM rust:1.93-slim-bookworm AS chef -# Install build dependencies. RocksDB is compiled from source by librocksdb-sys. -RUN apt-get update && \ - apt-get -y upgrade && \ - apt-get install -y \ - llvm \ - clang \ - libclang-dev \ - cmake \ - pkg-config \ - libssl-dev \ - libsqlite3-dev \ - ca-certificates && \ - rm -rf /var/lib/apt/lists/* -RUN cargo install cargo-chef -WORKDIR /app - -FROM chef AS planner -COPY . . -RUN cargo chef prepare --recipe-path recipe.json - -FROM chef AS builder -COPY --from=planner /app/recipe.json recipe.json -# Build dependencies - this is the caching Docker layer! -RUN cargo chef cook --release --recipe-path recipe.json -# Build application -COPY . . -RUN cargo build --release --locked --bin miden-validator - -# Base line runtime image with runtime dependencies installed. -FROM debian:bookworm-slim AS runtime-base -RUN apt-get update && \ - apt-get -y upgrade && \ - apt-get install -y --no-install-recommends sqlite3 ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -FROM runtime-base AS runtime -COPY --from=builder /app/target/release/miden-validator /usr/local/bin/miden-validator -LABEL org.opencontainers.image.authors=devops@miden.team \ - org.opencontainers.image.url=https://0xMiden.github.io/ \ - org.opencontainers.image.documentation=https://github.com/0xMiden/node \ - org.opencontainers.image.source=https://github.com/0xMiden/node \ - org.opencontainers.image.vendor=Miden \ - org.opencontainers.image.licenses=MIT -ARG CREATED -ARG VERSION -ARG COMMIT -LABEL org.opencontainers.image.created=$CREATED \ - org.opencontainers.image.version=$VERSION \ - org.opencontainers.image.revision=$COMMIT - -# Validator port is configurable via --url; 50101 is the default used in docker-compose. -EXPOSE 50101 -CMD ["miden-validator"] From ca1662806df874b6c85d912eb3dfa2716cf87b90 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 7 May 2026 12:27:48 +1200 Subject: [PATCH 12/16] Fix docker build / push CI --- .github/workflows/build-docker.yml | 18 ++++++++++++++++-- .github/workflows/publish-docker.yml | 13 +++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index e2e2e15e84..0512189d1c 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -17,11 +17,25 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - name: Build and push + - name: Build node uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: push: false - file: ./bin/node/Dockerfile + file: ./Dockerfile + build-args: | + BIN=miden-node + PORT=57291 cache-from: type=gha # Only save cache on push into next cache-to: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' && 'type=gha,mode=max' || '' }} + + - name: Build validator + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + with: + push: false + file: ./Dockerfile + build-args: | + BIN=miden-validator + PORT=50101 + cache-from: type=gha + cache-to: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' && 'type=gha,mode=max' || '' }} diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index c23d5a5a1d..c82fdf5c88 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -28,7 +28,13 @@ jobs: environment: publish-docker strategy: matrix: - component: [node] + include: + - component: node + bin: miden-node + port: 57291 + - component: validator + bin: miden-validator + port: 50101 name: Publish ${{ matrix.component }} ${{ inputs.version }} steps: - name: Checkout repo @@ -62,7 +68,10 @@ jobs: uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 with: push: true - file: ./bin/${{ matrix.component }}/Dockerfile + file: ./Dockerfile + build-args: | + BIN=${{ matrix.bin }} + PORT=${{ matrix.port }} tags: ${{ env.registry }}/0xmiden/miden-${{ matrix.component }}:${{ env.version }} cache-from: type=s3,region=${{ secrets.AWS_REGION }},bucket=${{ secrets.AWS_CACHE_BUCKET }},name=miden-${{ matrix.component }} cache-to: type=s3,region=${{ secrets.AWS_REGION }},bucket=${{ secrets.AWS_CACHE_BUCKET }},name=miden-${{ matrix.component }},mode=max From 88635633cbb052c6fcb977a4e8e8b1cbd73e450a Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 7 May 2026 12:40:08 +1200 Subject: [PATCH 13/16] Run docker build CI in parallel --- .github/workflows/build-docker.yml | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 0512189d1c..2f84ea3662 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -13,29 +13,28 @@ permissions: jobs: docker-build: runs-on: Linux-ARM64-Runner + strategy: + matrix: + include: + - component: node + bin: miden-node + port: 57291 + - component: validator + bin: miden-validator + port: 50101 + name: Build ${{ matrix.component }} steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - name: Build node + - name: Build uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: push: false file: ./Dockerfile build-args: | - BIN=miden-node - PORT=57291 + BIN=${{ matrix.bin }} + PORT=${{ matrix.port }} cache-from: type=gha # Only save cache on push into next cache-to: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' && 'type=gha,mode=max' || '' }} - - - name: Build validator - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 - with: - push: false - file: ./Dockerfile - build-args: | - BIN=miden-validator - PORT=50101 - cache-from: type=gha - cache-to: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' && 'type=gha,mode=max' || '' }} From 762a3e7bd222e67c4eec266d0350e4096ecf6794 Mon Sep 17 00:00:00 2001 From: sergerad Date: Fri, 8 May 2026 12:28:35 +1200 Subject: [PATCH 14/16] Fix remaining bad references to command --- bin/genesis/README.md | 2 +- bin/node/src/commands/store.rs | 2 +- scripts/run-node.sh | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bin/genesis/README.md b/bin/genesis/README.md index ddd9f0a5fc..8b570b77f6 100644 --- a/bin/genesis/README.md +++ b/bin/genesis/README.md @@ -38,7 +38,7 @@ The bridge account always uses NoAuth and has no secret keys. miden-genesis --output-dir ./genesis # 2. Bootstrap the genesis block -miden-node validator bootstrap \ +miden-validator bootstrap \ --genesis-block-directory ./data \ --accounts-directory ./accounts \ --genesis-config-file ./genesis/genesis.toml \ diff --git a/bin/node/src/commands/store.rs b/bin/node/src/commands/store.rs index 53ce600548..1f740f8756 100644 --- a/bin/node/src/commands/store.rs +++ b/bin/node/src/commands/store.rs @@ -24,7 +24,7 @@ const ENV_BLOCK_PROVER_URL: &str = "MIDEN_NODE_STORE_BLOCK_PROVER_URL"; pub enum StoreCommand { /// Bootstraps the blockchain database with a pre-existing genesis block. /// - /// The genesis block file should be produced by `miden-node validator bootstrap`. + /// The genesis block file should be produced by `miden-validator bootstrap`. Bootstrap { /// Directory in which to store the database and raw block data. #[arg(long, env = ENV_DATA_DIRECTORY, value_name = "DIR")] diff --git a/scripts/run-node.sh b/scripts/run-node.sh index a9621917ac..7414c6c2ea 100755 --- a/scripts/run-node.sh +++ b/scripts/run-node.sh @@ -4,6 +4,7 @@ set -euo pipefail # Configuration SKIP_BOOTSTRAP="${SKIP_BOOTSTRAP:-false}" BINARY="${MIDEN_NODE_BIN:-./target/debug/miden-node}" +VALIDATOR_BINARY="${MIDEN_VALIDATOR_BIN:-./target/debug/miden-validator}" KMS_KEY_ID="${KMS_KEY_ID:-}" if [[ -n "$KMS_KEY_ID" ]]; then AWS_REGION="${AWS_REGION:?error: AWS_REGION environment variable must be set when KMS_KEY_ID is set}" @@ -75,7 +76,7 @@ if [[ "$SKIP_BOOTSTRAP" != "true" ]]; then KMS_BOOTSTRAP_ARGS+=(--validator.key.kms-id "$KMS_KEY_ID") fi - $BINARY validator bootstrap \ + $VALIDATOR_BINARY bootstrap \ --data-directory "$VALIDATOR_DIR" \ --genesis-block-directory "$VALIDATOR_DIR" \ --accounts-directory "$ACCOUNTS_DIR" \ @@ -118,7 +119,7 @@ if [[ -n "$KMS_KEY_ID" ]]; then fi echo "Starting validator..." -$BINARY validator start "$VALIDATOR_URL" \ +$VALIDATOR_BINARY start "$VALIDATOR_URL" \ --data-directory "$VALIDATOR_DIR" \ "${KMS_START_ARGS[@]+"${KMS_START_ARGS[@]}"}" & PIDS+=($!) From 55ca4990772db3ca506198fd85273b6b8ac661a1 Mon Sep 17 00:00:00 2001 From: sergerad Date: Fri, 8 May 2026 12:39:22 +1200 Subject: [PATCH 15/16] Rename validator lib crate to miden-validator-core --- Cargo.lock | 58 +++++++++++------------ Cargo.toml | 2 +- bin/validator/Cargo.toml | 2 +- bin/validator/src/commands/bootstrap.rs | 6 +-- bin/validator/src/commands/mod.rs | 2 +- bin/validator/src/commands/start.rs | 2 +- crates/block-producer/Cargo.toml | 2 +- crates/block-producer/src/server/tests.rs | 2 +- crates/validator/Cargo.toml | 2 +- 9 files changed, 39 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f28cc27ef2..1f3ea6aa44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3253,11 +3253,11 @@ dependencies = [ "miden-node-proto-build", "miden-node-store", "miden-node-utils", - "miden-node-validator", "miden-protocol", "miden-remote-prover-client", "miden-standards", "miden-tx-batch-prover", + "miden-validator-core", "pretty_assertions", "rand 0.9.2", "rand_chacha", @@ -3506,33 +3506,6 @@ dependencies = [ "url", ] -[[package]] -name = "miden-node-validator" -version = "0.15.0" -dependencies = [ - "anyhow", - "aws-config", - "aws-sdk-kms", - "build-rs", - "diesel", - "diesel_migrations", - "miden-node-db", - "miden-node-proto", - "miden-node-proto-build", - "miden-node-store", - "miden-node-utils", - "miden-protocol", - "miden-tx", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tonic", - "tonic-reflection", - "tower-http", - "tracing", -] - [[package]] name = "miden-package-registry" version = "0.22.1" @@ -3811,12 +3784,39 @@ dependencies = [ "hex", "miden-node-store", "miden-node-utils", - "miden-node-validator", "miden-protocol", + "miden-validator-core", "tokio", "url", ] +[[package]] +name = "miden-validator-core" +version = "0.15.0" +dependencies = [ + "anyhow", + "aws-config", + "aws-sdk-kms", + "build-rs", + "diesel", + "diesel_migrations", + "miden-node-db", + "miden-node-proto", + "miden-node-proto-build", + "miden-node-store", + "miden-node-utils", + "miden-protocol", + "miden-tx", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tonic", + "tonic-reflection", + "tower-http", + "tracing", +] + [[package]] name = "miden-verifier" version = "0.22.1" diff --git a/Cargo.toml b/Cargo.toml index a3cba7c47b..c68092f1b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,8 +55,8 @@ miden-node-rpc = { path = "crates/rpc", version = "0.15" } miden-node-store = { path = "crates/store", version = "0.15" } miden-node-test-macro = { path = "crates/test-macro" } miden-node-utils = { path = "crates/utils", version = "0.15" } -miden-node-validator = { path = "crates/validator", version = "0.15" } miden-remote-prover-client = { path = "crates/remote-prover-client", version = "0.15" } +miden-validator-core = { path = "crates/validator", version = "0.15" } # Temporary workaround until # is part of `rocksdb-rust` release miden-node-rocksdb-cxx-linkage-fix = { path = "crates/rocksdb-cxx-linkage-fix", version = "0.15" } diff --git a/bin/validator/Cargo.toml b/bin/validator/Cargo.toml index 3d4d5c1b32..32bc69fc1a 100644 --- a/bin/validator/Cargo.toml +++ b/bin/validator/Cargo.toml @@ -21,7 +21,7 @@ fs-err = { workspace = true } hex = { workspace = true } miden-node-store = { workspace = true } miden-node-utils = { workspace = true } -miden-node-validator = { workspace = true } miden-protocol = { workspace = true } +miden-validator-core = { workspace = true } tokio = { features = ["macros", "rt-multi-thread"], workspace = true } url = { workspace = true } diff --git a/bin/validator/src/commands/bootstrap.rs b/bin/validator/src/commands/bootstrap.rs index f4f95c0b37..3eb5a287a9 100644 --- a/bin/validator/src/commands/bootstrap.rs +++ b/bin/validator/src/commands/bootstrap.rs @@ -4,8 +4,8 @@ use anyhow::Context; use miden_node_store::genesis::config::{AccountFileWithName, GenesisConfig}; use miden_node_utils::fs::ensure_empty_directory; use miden_node_utils::signer::BlockSigner; -use miden_node_validator::ValidatorSigner; use miden_protocol::utils::serde::Serializable; +use miden_validator_core::ValidatorSigner; use super::ValidatorKey; @@ -88,11 +88,11 @@ async fn build_and_write_genesis( fs_err::write(&genesis_block_path, block_bytes).context("failed to write genesis block")?; let (genesis_header, ..) = genesis_block.into_inner().into_parts(); - let db = miden_node_validator::db::load(data_directory.join("validator.sqlite3")) + let db = miden_validator_core::db::load(data_directory.join("validator.sqlite3")) .await .context("failed to initialize validator database during bootstrap")?; db.transact("upsert_block_header", move |conn| { - miden_node_validator::db::upsert_block_header(conn, &genesis_header) + miden_validator_core::db::upsert_block_header(conn, &genesis_header) }) .await .context("failed to persist genesis block header as chain tip")?; diff --git a/bin/validator/src/commands/mod.rs b/bin/validator/src/commands/mod.rs index 67c01b41a5..ea81923d69 100644 --- a/bin/validator/src/commands/mod.rs +++ b/bin/validator/src/commands/mod.rs @@ -7,9 +7,9 @@ use anyhow::Context; use clap::Parser; use miden_node_utils::clap::GrpcOptionsInternal; use miden_node_utils::grpc::UrlExt; -use miden_node_validator::ValidatorSigner; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; use miden_protocol::utils::serde::Deserializable; +use miden_validator_core::ValidatorSigner; use url::Url; pub(crate) const ENV_DATA_DIRECTORY: &str = "MIDEN_NODE_DATA_DIRECTORY"; diff --git a/bin/validator/src/commands/start.rs b/bin/validator/src/commands/start.rs index c8d1273c3d..994f478937 100644 --- a/bin/validator/src/commands/start.rs +++ b/bin/validator/src/commands/start.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use anyhow::Context; use miden_node_utils::clap::GrpcOptionsInternal; -use miden_node_validator::{Validator, ValidatorSigner}; +use miden_validator_core::{Validator, ValidatorSigner}; // Starts the validator component. pub async fn start( diff --git a/crates/block-producer/Cargo.toml b/crates/block-producer/Cargo.toml index d3349bf7cc..9e589834b5 100644 --- a/crates/block-producer/Cargo.toml +++ b/crates/block-producer/Cargo.toml @@ -46,9 +46,9 @@ url = { workspace = true } assert_matches = { workspace = true } miden-node-store = { workspace = true } miden-node-utils = { features = ["testing"], workspace = true } -miden-node-validator = { workspace = true } miden-protocol = { default-features = true, features = ["testing"], workspace = true } miden-standards = { features = ["testing"], workspace = true } +miden-validator-core = { workspace = true } pretty_assertions = { workspace = true } rand_chacha = { default-features = false, workspace = true } serial_test = { workspace = true } diff --git a/crates/block-producer/src/server/tests.rs b/crates/block-producer/src/server/tests.rs index c3b1d40039..b5b981621a 100644 --- a/crates/block-producer/src/server/tests.rs +++ b/crates/block-producer/src/server/tests.rs @@ -5,8 +5,8 @@ use miden_node_proto::generated::block_producer::api_client as block_producer_cl use miden_node_store::{DEFAULT_MAX_CONCURRENT_PROOFS, GenesisState, Store, StoreMode}; use miden_node_utils::clap::{GrpcOptionsInternal, StorageOptions}; use miden_node_utils::fee::test_fee_params; -use miden_node_validator::{Validator, ValidatorSigner}; use miden_protocol::testing::random_secret_key::random_secret_key; +use miden_validator_core::{Validator, ValidatorSigner}; use tokio::net::TcpListener; use tokio::time::sleep; use tokio::{runtime, task}; diff --git a/crates/validator/Cargo.toml b/crates/validator/Cargo.toml index bbe4d4df0a..65ed5d8204 100644 --- a/crates/validator/Cargo.toml +++ b/crates/validator/Cargo.toml @@ -5,7 +5,7 @@ edition.workspace = true homepage.workspace = true keywords = ["miden", "node", "validator"] license.workspace = true -name = "miden-node-validator" +name = "miden-validator-core" readme = "README.md" repository.workspace = true rust-version.workspace = true From bd92bd666c37e7d677f73e1bcc74d873b7ca10e9 Mon Sep 17 00:00:00 2001 From: sergerad Date: Fri, 8 May 2026 12:41:26 +1200 Subject: [PATCH 16/16] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d8c317b87..c9f6a2aea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,8 @@ - Added `StoreReplica` gRPC service with endpoints for streaming blocks and proofs ([#1987](https://github.com/0xMiden/node/pull/1987)). - Replaced the network monitor's JavaScript dashboard with a server-rendered Maud + HTMX frontend ([#2024](https://github.com/0xMiden/node/pull/2024)). - [BREAKING] Removed `CheckNullifiers` endpoint ([#2049](https://github.com/0xMiden/node/pull/2049)). -- Removed `miden-node validator` subcommand and created a separate `miden-validator` binary ([#2053](https://github.com/0xMiden/node/pull/2053)). - [BREAKING] `BlockRange.block_to` is now required for all RPC endpoints ([#2056](https://github.com/0xMiden/node/pull/2056)). +- [BREAKING] Removed `miden-node validator` subcommand and created a separate `miden-validator` binary ([#2053](https://github.com/0xMiden/node/pull/2053)). ## v0.14.10 (2026-05-29)