Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7b8dc3f
Move store spawns to block producer.
Mirko-von-Leipzig May 28, 2026
7983818
Task supervisor
Mirko-von-Leipzig May 28, 2026
361dddf
fixup
Mirko-von-Leipzig May 28, 2026
4a30be4
Move supervisor error to join set
Mirko-von-Leipzig May 29, 2026
677ebc3
Simplify supervised taskset
Mirko-von-Leipzig May 29, 2026
a8dc3af
Review comment
Mirko-von-Leipzig May 29, 2026
1cb9dba
Merge remote-tracking branch 'origin/next' into mirko/move-sync
Mirko-von-Leipzig May 29, 2026
2ccfe25
Simplify bootstrapping using sentinels
Mirko-von-Leipzig May 29, 2026
1a6deb5
Docker compose: use sequencer mode
Mirko-von-Leipzig May 29, 2026
8307da6
Simplify the otel setup
Mirko-von-Leipzig May 29, 2026
997a7d5
Small improvements
Mirko-von-Leipzig May 29, 2026
d90e095
Hide validator port
Mirko-von-Leipzig May 29, 2026
8637589
Update `run-node.sh`
Mirko-von-Leipzig May 29, 2026
b369607
OTel attributes are now handled in the code
Mirko-von-Leipzig May 29, 2026
f541a6e
Simplify OTel CLI
Mirko-von-Leipzig May 29, 2026
f9291e8
Also do prover
Mirko-von-Leipzig May 29, 2026
41a6964
Merge remote-tracking branch 'origin/next' into mirko/update-docker
Mirko-von-Leipzig May 30, 2026
0579913
Merge branch 'next' into mirko/update-docker
Mirko-von-Leipzig May 30, 2026
97b742d
Ensure ntx data directory is created.
Mirko-von-Leipzig May 30, 2026
47d0947
Support bootstrap for official networks
Mirko-von-Leipzig May 30, 2026
4cca0df
Update docs and scripts
Mirko-von-Leipzig May 30, 2026
c3b7b21
Merge remote-tracking branch 'origin/next' into mirko/bootstrap-network
Mirko-von-Leipzig Jun 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

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

8 changes: 5 additions & 3 deletions bin/genesis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ miden-validator bootstrap \
--genesis-config-file ./genesis/genesis.toml \
--validator.key.hex <validator_key>

# 3. Bootstrap the store
miden-node store bootstrap --data-directory ./data
# 3. Bootstrap the node
miden-node bootstrap \
--data-directory ./node-data \
--file ./data/genesis.dat

# 4. Start the node
miden-node bundled start --data-directory ./data ...
miden-node sequencer --data-directory ./node-data ...
```

## TODO
Expand Down
1 change: 0 additions & 1 deletion bin/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ tracing-forest = ["miden-node-block-producer/tracing-forest"]
[dependencies]
anyhow = { workspace = true }
clap = { features = ["env", "string"], workspace = true }
fs-err = { workspace = true }
humantime = { workspace = true }
miden-node-block-producer = { workspace = true }
miden-node-proto = { workspace = true }
Expand Down
47 changes: 36 additions & 11 deletions bin/node/src/commands/lifecycle.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,65 @@
use std::path::{Path, PathBuf};

use anyhow::Context;
use clap::ArgGroup;
use miden_node_store::genesis::GenesisBlock;
use miden_node_store::{DataDirectory, Db, State};
use miden_node_utils::fs::ensure_empty_directory;
use miden_node_utils::genesis::{
OfficialNetwork,
fetch_signed_genesis_block,
read_signed_genesis_block,
};
use miden_protocol::block::SignedBlock;
use miden_protocol::utils::serde::Deserializable;

use super::ENV_DATA_DIRECTORY;

// BOOTSTRAP
// ================================================================================================

#[derive(clap::Args, Clone, Debug)]
#[command(group(
ArgGroup::new("genesis_block_source")
.required(true)
.multiple(false)
.args(["genesis_block_file", "network"])
))]
pub struct BootstrapCommand {
/// Directory to initialize with the node's local data storage.
#[arg(long, env = ENV_DATA_DIRECTORY, value_name = "DIR")]
data_directory: PathBuf,

/// Path to the trusted, signed genesis block file.
#[arg(long, value_name = "FILE")]
genesis_block: PathBuf,
/// Bootstrap from a trusted genesis block file.
#[arg(long = "file", value_name = "FILE")]
genesis_block_file: Option<PathBuf>,

/// Bootstrap for an official Miden network.
#[arg(long, value_enum, value_name = "NETWORK")]
network: Option<OfficialNetwork>,
}

impl BootstrapCommand {
pub fn handle(self) -> anyhow::Result<()> {
pub async fn handle(self) -> anyhow::Result<()> {
ensure_empty_directory(&self.data_directory)?;
bootstrap_store(&self.data_directory, &self.genesis_block)
let signed_block =
read_bootstrap_genesis_block(self.genesis_block_file.as_deref(), self.network).await?;
bootstrap_store(&self.data_directory, signed_block)
}
}

async fn read_bootstrap_genesis_block(
genesis_block_file: Option<&Path>,
network: Option<OfficialNetwork>,
) -> anyhow::Result<SignedBlock> {
match (genesis_block_file, network) {
(Some(path), None) => read_signed_genesis_block(path),
(None, Some(network)) => fetch_signed_genesis_block(network).await,
_ => unreachable!("clap requires exactly one genesis block source"),
}
}

/// Reads a genesis block from disk, validates it, and bootstraps the store.
pub fn bootstrap_store(data_directory: &Path, genesis_block_path: &Path) -> anyhow::Result<()> {
let bytes = fs_err::read(genesis_block_path).context("failed to read genesis block")?;
let signed_block = SignedBlock::read_from_bytes(&bytes)
.context("failed to deserialize genesis block from file")?;
/// Validates a signed genesis block and bootstraps the store.
pub fn bootstrap_store(data_directory: &Path, signed_block: SignedBlock) -> anyhow::Result<()> {
let genesis_block =
GenesisBlock::try_from(signed_block).context("genesis block validation failed")?;

Expand Down
2 changes: 1 addition & 1 deletion bin/node/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl Command {

pub(crate) async fn execute(self) -> anyhow::Result<()> {
match self {
Command::Bootstrap(bootstrap_command) => bootstrap_command.handle(),
Command::Bootstrap(bootstrap_command) => bootstrap_command.handle().await,
Command::Migrate(migrate_command) => migrate_command.handle().await,
Command::Sequencer(sequencer_command) => sequencer_command.handle().await,
Command::Full(full_node_command) => full_node_command.handle().await,
Expand Down
1 change: 0 additions & 1 deletion bin/ntx-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ anyhow = { workspace = true }
backon = { workspace = true }
clap = { features = ["env", "string"], workspace = true }
diesel = { features = ["numeric", "sqlite"], workspace = true }
fs-err = { workspace = true }
futures = { workspace = true }
humantime = { workspace = true }
libsqlite3-sys = { workspace = true }
Expand Down
58 changes: 44 additions & 14 deletions bin/ntx-builder/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ use std::path::{Path, PathBuf};
use std::time::Duration;

use anyhow::Context;
use clap::Parser;
use clap::{ArgGroup, Parser};
use miden_node_utils::clap::duration_to_human_readable_string;
use miden_node_utils::fs::ensure_empty_directory;
use miden_node_utils::genesis::{
OfficialNetwork,
fetch_signed_genesis_block,
read_signed_genesis_block,
};
use miden_node_utils::logging::OpenTelemetry;
use miden_protocol::block::SignedBlock;
use miden_protocol::utils::serde::Deserializable;
use tokio::net::TcpListener;
use tonic::metadata::AsciiMetadataValue;
use url::Url;
Expand Down Expand Up @@ -128,25 +132,40 @@ pub enum NtxBuilderCommand {
///
/// This must be run once before `start` so that the database always contains at least the
/// genesis block.
#[command(group(
ArgGroup::new("genesis_block_source")
.required(true)
.multiple(false)
.args(["genesis_block_file", "network"])
))]
Bootstrap {
/// Directory for the ntx-builder's persistent database.
#[arg(long = "data-directory", env = ENV_DATA_DIRECTORY, value_name = "DIR")]
data_directory: PathBuf,

/// Path to the trusted, signed genesis block file.
#[arg(long, value_name = "FILE")]
genesis_block: PathBuf,
/// Bootstrap from a trusted genesis block file.
#[arg(long = "file", value_name = "FILE")]
genesis_block_file: Option<PathBuf>,

/// Bootstrap for an official Miden network.
#[arg(long, value_enum, value_name = "NETWORK")]
network: Option<OfficialNetwork>,
},
}

impl NtxBuilderCommand {
pub async fn handle(self) -> anyhow::Result<()> {
match self {
Self::Start { .. } => self.start().await,
Self::Bootstrap { data_directory, genesis_block } => {
Self::Bootstrap {
data_directory,
genesis_block_file,
network,
} => {
ensure_empty_directory(&data_directory)?;
let database_filepath = data_directory.join("ntx-builder.sqlite3");
let genesis = read_genesis_block(&genesis_block)?;
let genesis =
read_bootstrap_genesis_block(genesis_block_file.as_deref(), network).await?;
miden_ntx_builder::bootstrap(database_filepath, &genesis)
.await
.context("failed to bootstrap ntx-builder database")
Expand Down Expand Up @@ -209,10 +228,15 @@ impl NtxBuilderCommand {
}
}

/// Reads a genesis block from disk and returns the signed block.
fn read_genesis_block(genesis_block_path: &Path) -> anyhow::Result<SignedBlock> {
let bytes = fs_err::read(genesis_block_path).context("failed to read genesis block")?;
SignedBlock::read_from_bytes(&bytes).context("failed to deserialize genesis block from file")
async fn read_bootstrap_genesis_block(
genesis_block_file: Option<&Path>,
network: Option<OfficialNetwork>,
) -> anyhow::Result<SignedBlock> {
match (genesis_block_file, network) {
(Some(path), None) => read_signed_genesis_block(path),
(None, Some(network)) => fetch_signed_genesis_block(network).await,
_ => unreachable!("clap requires exactly one genesis block source"),
}
}

#[cfg(test)]
Expand Down Expand Up @@ -254,16 +278,22 @@ mod tests {
"bootstrap",
"--data-directory",
"/tmp/miden-ntx-builder",
"--genesis-block",
"--file",
"/tmp/genesis.dat",
])
.expect("command should parse");

let NtxBuilderCommand::Bootstrap { data_directory, genesis_block } = command else {
let NtxBuilderCommand::Bootstrap {
data_directory,
genesis_block_file,
network,
} = command
else {
panic!("expected the bootstrap command");
};

assert_eq!(data_directory, PathBuf::from("/tmp/miden-ntx-builder"));
assert_eq!(genesis_block, PathBuf::from("/tmp/genesis.dat"));
assert_eq!(genesis_block_file, Some(PathBuf::from("/tmp/genesis.dat")));
assert_eq!(network, None);
}
}
1 change: 1 addition & 0 deletions crates/utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ opentelemetry = { workspace = true }
opentelemetry-otlp = { default-features = false, features = ["grpc-tonic", "tls-roots", "trace"], version = "0.31" }
opentelemetry_sdk = { features = ["rt-tokio", "testing"], version = "0.31" }
rand = { workspace = true }
reqwest = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tonic = { default-features = true, workspace = true }
Expand Down
58 changes: 58 additions & 0 deletions crates/utils/src/genesis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::fmt;
use std::path::Path;

use anyhow::Context;
use miden_protocol::block::SignedBlock;
use miden_protocol::utils::serde::Deserializable;

/// Official Miden networks with a hosted genesis block.
#[derive(clap::ValueEnum, Clone, Copy, Debug, Eq, PartialEq)]
pub enum OfficialNetwork {
Devnet,
Testnet,
}

impl OfficialNetwork {
pub const fn as_str(self) -> &'static str {
match self {
Self::Devnet => "devnet",
Self::Testnet => "testnet",
}
}

pub fn genesis_block_url(self) -> String {
format!("https://genesis.{}.miden.io", self.as_str())
}
}

impl fmt::Display for OfficialNetwork {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}

/// Reads a trusted, signed genesis block from disk.
pub fn read_signed_genesis_block(path: &Path) -> anyhow::Result<SignedBlock> {
let bytes = fs_err::read(path).context("failed to read genesis block file")?;
deserialize_signed_genesis_block(&bytes)
}

/// Downloads a trusted, signed genesis block for an official Miden network.
pub async fn fetch_signed_genesis_block(network: OfficialNetwork) -> anyhow::Result<SignedBlock> {
let url = network.genesis_block_url();
let response = reqwest::get(url.as_str())
.await
.with_context(|| format!("failed to fetch genesis block from {url}"))?
.error_for_status()
.with_context(|| format!("failed to fetch genesis block from {url}"))?;
let bytes = response
.bytes()
.await
.with_context(|| format!("failed to read genesis block response from {url}"))?;

deserialize_signed_genesis_block(&bytes)
}

fn deserialize_signed_genesis_block(bytes: &[u8]) -> anyhow::Result<SignedBlock> {
SignedBlock::read_from_bytes(bytes).context("failed to deserialize genesis block")
}
1 change: 1 addition & 0 deletions crates/utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod fee;
pub mod fifo_cache;
pub mod formatting;
pub mod fs;
pub mod genesis;
pub mod grpc;
pub mod limiter;
pub mod logging;
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ services:
echo "Bootstrapping node..."
miden-node bootstrap \
--data-directory /data/node \
--genesis-block /data/genesis/genesis.dat
--file /data/genesis/genesis.dat

touch /data/node/.bootstrapped

Expand Down Expand Up @@ -77,7 +77,7 @@ services:
echo "Bootstrapping network transaction builder..."
miden-ntx-builder bootstrap \
--data-directory /data/ntx-builder \
--genesis-block /data/genesis/genesis.dat
--file /data/genesis/genesis.dat

touch /data/ntx-builder/.bootstrapped

Expand Down
6 changes: 3 additions & 3 deletions docs/external/src/operator/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ miden-validator bootstrap \
--genesis-block-directory genesis-data \
--accounts-directory accounts

# Step 2: Store bootstrap — initialize the store database from the genesis block.
miden-node store bootstrap \
# Step 2: Node bootstrap — initialize the node database from the genesis block.
miden-node bootstrap \
--data-directory store-data \
--genesis-block genesis-data/genesis.dat
--file genesis-data/genesis.dat
```

You can also configure the account and asset data in the genesis block by passing in a toml configuration file.
Expand Down
4 changes: 2 additions & 2 deletions scripts/run-node.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,15 @@ bootstrap_node_data_dir() {
echo "Bootstrapping $label..."
"$NODE_BINARY" bootstrap \
--data-directory "$data_dir" \
--genesis-block "$VALIDATOR_DIR/genesis.dat"
--file "$VALIDATOR_DIR/genesis.dat"
}

bootstrap_ntx_builder() {
echo "Bootstrapping network transaction builder..."

"$NTX_BUILDER_BINARY" bootstrap \
--data-directory "$NTX_BUILDER_DIR" \
--genesis-block "$VALIDATOR_DIR/genesis.dat"
--file "$VALIDATOR_DIR/genesis.dat"
}

node_resource_attributes() {
Expand Down
Loading